Completed
Push — master ( 8aed6c...4bc51d )
by Gaetano
07:19
created

RepositoryExecutor::insureSingleEntity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 2
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\ReferenceResolverBagInterface;
9
use Kaliop\eZMigrationBundle\API\Value\MigrationStep;
10
use Kaliop\eZMigrationBundle\Core\RepositoryUserSetterTrait;
11
12
/**
13
 * The core manager class that all migration action managers inherit from.
14
 */
15
abstract class RepositoryExecutor extends AbstractExecutor
16
{
17
    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...
18
    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...
19
20
    /**
21
     * Constant defining the default language code (used if not specified by the migration or on the command line)
22
     */
23
    const DEFAULT_LANGUAGE_CODE = 'eng-GB';
24
25
    /**
26
     * The default Admin user Id, used when no Admin user is specified
27
     */
28
    const ADMIN_USER_ID = 14;
29
30
    /** Used if not specified by the migration */
31
    const USER_CONTENT_TYPE = 'user';
32
    /** Used if not specified by the migration */
33
    const USERGROUP_CONTENT_TYPE = 'user_group';
34
35
    const REFERENCE_TYPE_SCALAR = 'scalar';
36
    const REFERENCE_TYPE_ARRAY = 'array';
37
38
    /**
39
     * @var array $dsl The parsed DSL instruction array
40
     */
41
    //protected $dsl;
42
43
    /** @var array $context The context (configuration) for the execution of the current step */
44
    //protected $context;
45
46
    /**
47
     * The eZ Publish 5 API repository.
48
     *
49
     * @var \eZ\Publish\API\Repository\Repository
50
     */
51
    protected $repository;
52
53
    protected $configResolver;
54
55
    /** @var ReferenceResolverBagInterface $referenceResolver */
56
    protected $referenceResolver;
57
58
    // to redefine in subclasses if they don't support all methods, or if they support more...
59
    protected $supportedActions = array(
60
        'create', 'update', 'delete'
61 96
    );
62
63 96
    public function setRepository(Repository $repository)
64 96
    {
65
        $this->repository = $repository;
66 96
    }
67
68 96
    public function setConfigResolver(ConfigResolverInterface $configResolver)
69 96
    {
70
        $this->configResolver = $configResolver;
71 96
    }
72
73 96
    public function setReferenceResolver(ReferenceResolverBagInterface $referenceResolver)
74 96
    {
75
        $this->referenceResolver = $referenceResolver;
76 24
    }
77
78
    public function execute(MigrationStep $step)
79 24
    {
80
        // base checks
81 24
        parent::execute($step);
82
83
        if (!isset($step->dsl['mode'])) {
84
            throw new \Exception("Invalid step definition: missing 'mode'");
85
        }
86 24
87
        // q: should we convert snake_case to camelCase ?
88 24
        $action = $step->dsl['mode'];
89
90
        if (!in_array($action, $this->supportedActions)) {
91
            throw new \Exception("Invalid step definition: value '$action' is not allowed for 'mode'");
92 24
        }
93
94 24
        if (method_exists($this, $action)) {
95
96 24
            $this->skipStepIfNeeded($step);
97
98
            $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($step->context));
99 24
100 6
            try {
101 6
                $output = $this->$action($step);
102 6
            } catch (\Exception $e) {
103
                $this->loginUser($previousUserId);
104
                throw $e;
105
            }
106 22
107
            // reset the environment as much as possible as we had found it before the migration
108 22
            $this->loginUser($previousUserId);
109
110
            return $output;
111
        } else {
112
            throw new \Exception("Invalid step definition: value '$action' is not a method of " . get_class($this));
113
        }
114
    }
115
116
    /**
117
     * Method that each executor (subclass) has to implement.
118
     *
119
     * It is used to get values for references based on the DSL instructions executed in the current step, for later steps to reuse.
120
     *
121
     * @throws \InvalidArgumentException when trying to set a reference to an unsupported attribute.
122
     * @param mixed $object a single element to extract reference values from
123
     * @param array $referencesDefinitions the definitions of the references to extract
124
     * @param MigrationStep $step
125
     * @return array key: the reference name (taken from $referencesDefinitions[n]['identifier'], value: the ref. value
126
     */
127
    abstract protected function getReferencesValues($object, array $referencesDefinitions, $step);
128
129
    /**
130
     * @param MigrationStep $step
131 17
     * @return string
132
     */
133 17
    protected function getLanguageCode($step)
134
    {
135
        return isset($step->dsl['lang']) ? $step->dsl['lang'] : $this->getLanguageCodeFromContext($step->context);
136
    }
137
138
    /**
139
     * @param array|null $context
140 22
     * @return string
141
     */
142 22
    protected function getLanguageCodeFromContext($context)
143 1
    {
144
        if (is_array($context) && isset($context['defaultLanguageCode'])) {
145
            return $context['defaultLanguageCode'];
146 21
        }
147 21
148 21
        if ($this->configResolver) {
149
            $locales = $this->configResolver->getParameter('languages');
150
            return reset($locales);
151
        }
152
153
        return self::DEFAULT_LANGUAGE_CODE;
154
    }
155
156
    /**
157
     * @param MigrationStep $step
158 2
     * @return string
159
     */
160 2
    protected function getUserContentType($step)
161
    {
162
        return isset($step->dsl['user_content_type']) ? $this->referenceResolver->resolveReference($step->dsl['user_content_type']) : $this->getUserContentTypeFromContext($step->context);
163
    }
164
165
    /**
166
     * @param MigrationStep $step
167 2
     * @return string
168
     */
169 2
    protected function getUserGroupContentType($step)
170
    {
171
        return isset($step->dsl['usergroup_content_type']) ? $this->referenceResolver->resolveReference($step->dsl['usergroup_content_type']) : $this->getUserGroupContentTypeFromContext($step->context);
172
    }
173
174
    /**
175
     * @param array $context
176 52
     * @return string
177
     */
178 52
    protected function getUserContentTypeFromContext($context)
179
    {
180
        return isset($context['userContentType']) ? $context['userContentType'] : self::USER_CONTENT_TYPE;
181
    }
182 52
183
    /**
184
     * @param array $context
185
     * @return string
186
     */
187
    protected function getUserGroupContentTypeFromContext($context)
188
    {
189
        return isset($context['userGroupContentType']) ? $context['userGroupContentType'] : self::USERGROUP_CONTENT_TYPE;
190
    }
191
192
    /**
193
     * @param array $context we have to return FALSE if that is set as adminUserLogin, whereas if NULL is set, we return the default admin
194 23
     * @return int|string|false
195
     */
196 23
    protected function getAdminUserIdentifierFromContext($context)
197 20
    {
198
        if (isset($context['adminUserLogin'])) {
199
            return $context['adminUserLogin'];
200 18
        }
201
202 18
        return self::ADMIN_USER_ID;
203
    }
204 17
205
    /**
206 17
     * Sets references to certain attributes of the items returned by steps.
207 12
     *
208
     * @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...
209 16
     * @param MigrationStep $step
210
     * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute
211
     * @return boolean
212 17
     * @todo should we allow to be passed in plain arrays as well as Collections?
213 17
     */
214 17
    protected function setReferences($item, $step)
215 17
    {
216 16
        if (!array_key_exists('references', $step->dsl)) {
217
            return false;
218 1
        }
219 1
220 1
        $referencesDefs = $this->setReferencesCommon($item, $step->dsl['references']);
221
222 17
        $this->insureEntityCountCompatibility($item, $referencesDefs, $step);
223
224
        $multivalued = ($this->getReferencesType($step) == self::REFERENCE_TYPE_ARRAY);
0 ignored issues
show
Bug introduced by
$step of type Kaliop\eZMigrationBundle\API\Value\MigrationStep is incompatible with the type array expected by parameter $step of Kaliop\eZMigrationBundle...or::getReferencesType(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

224
        $multivalued = ($this->getReferencesType(/** @scrutinizer ignore-type */ $step) == self::REFERENCE_TYPE_ARRAY);
Loading history...
225
226
        if ($item instanceof AbstractCollection) {
227
            $items = $item;
228 17
        } else {
229 17
            $items = array($item);
230 17
        }
231 1
232
        $referencesValues = array();
233 17
        foreach ($items as $item) {
0 ignored issues
show
introduced by
$item is overwriting one of the parameters of this function.
Loading history...
234
            $itemReferencesValues = $this->getReferencesValues($item, $referencesDefs, $step);
235
            if (!$multivalued) {
236 17
                $referencesValues = $itemReferencesValues;
237
            } else {
238
                foreach ($itemReferencesValues as $refName => $refValue) {
239
                    if (!isset($referencesValues[$refName])) {
240
                        $referencesValues[$refName] = array($refValue);
241
                    } else {
242
                        $referencesValues[$refName][] = $refValue;
243
                    }
244 18
                }
245
            }
246
        }
247 18
248 18
        foreach($referencesDefs as $reference) {
249
            $overwrite = false;
250 18
            if (isset($reference['overwrite'])) {
251 4
                $overwrite = $reference['overwrite'];
252 4
            }
253 4
            $this->referenceResolver->addReference($reference['identifier'], count($referencesValues) ? $referencesValues[$reference['identifier']] : array(), $overwrite);
254 1
        }
255
256 4
        return true;
257 4
    }
258 4
259
    /**
260 18
     * @param mixed $entity
261
     * @param array $referencesDefinition
262
     * @return array the same as $referencesDefinition, with the references already treated having been removed
263
     */
264
    protected function setReferencesCommon($entity, $referencesDefinition)
265 18
    {
266
        // allow setting *some* refs even when we have 0 or N matches
267
        foreach ($referencesDefinition as $key => $reference) {
268
            switch($reference['attribute']) {
269
270
                case 'count':
271
                    $value = count($entity);
272
                    $overwrite = false;
273
                    if (isset($reference['overwrite'])) {
274
                        $overwrite = $reference['overwrite'];
275
                    }
276
                    $this->referenceResolver->addReference($reference['identifier'], $value, $overwrite);
277
                    unset($referencesDefinition[$key]);
278
                    break;
279
280
                default:
281
                    // do nothing
282
            }
283
        }
284
285
        return $referencesDefinition;
286
    }
287
288
    /**
289
     * Verifies compatibility between the definition of the refences to be set and the data set to extarct them from,
290
     * and returns a single entity
291
     *
292
     * @param AbstractCollection|mixed $entity
293
     * @param array $referencesDefinition
294
     * @param MigrationStep $step
295
     * @return AbstractCollection|mixed
296
     * @deprecated
297
     */
298 18
    protected function insureSingleEntity($entity, $referencesDefinition, $step)
299
    {
300 18
        $this->insureEntityCountCompatibility($entity, $referencesDefinition, $step);
301
302 13
        if ($entity instanceof AbstractCollection) {
303 13
            return reset($entity);
0 ignored issues
show
Bug introduced by
$entity of type Kaliop\eZMigrationBundle...tion\AbstractCollection is incompatible with the type array expected by parameter $array of reset(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

303
            return reset(/** @scrutinizer ignore-type */ $entity);
Loading history...
304
        }
305 13
306 1
        return $entity;
307
    }
308 12
309
    /**
310
     * Verifies compatibility between the definition of the references to be set and the data set to extract them from.
311
     * NB: for multivalued refs, we assume that the users always expect at least one value
312 17
     * @param AbstractCollection|mixed $entity
313
     * @param array $referencesDefinition
314
     * @param MigrationStep $step
315
     * @return void throws when incompatibility is found
316
     * @todo should we allow to be passed in plain arrays as well as Collections?
317
     */
318 18
    protected function insureEntityCountCompatibility($entity, $referencesDefinition, $step)
319
    {
320 18
        if ($entity instanceof AbstractCollection) {
321
322
            $minOneRef = count($referencesDefinition) > 0 && (!$this->allowEmptyReferences($step));
0 ignored issues
show
Bug introduced by
$step of type Kaliop\eZMigrationBundle\API\Value\MigrationStep is incompatible with the type array expected by parameter $step of Kaliop\eZMigrationBundle...:allowEmptyReferences(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

322
            $minOneRef = count($referencesDefinition) > 0 && (!$this->allowEmptyReferences(/** @scrutinizer ignore-type */ $step));
Loading history...
323
            $maxOneRef = count($referencesDefinition) > 0 && $this->getReferencesType($step) == self::REFERENCE_TYPE_SCALAR;
0 ignored issues
show
Bug introduced by
$step of type Kaliop\eZMigrationBundle\API\Value\MigrationStep is incompatible with the type array expected by parameter $step of Kaliop\eZMigrationBundle...or::getReferencesType(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

323
            $maxOneRef = count($referencesDefinition) > 0 && $this->getReferencesType(/** @scrutinizer ignore-type */ $step) == self::REFERENCE_TYPE_SCALAR;
Loading history...
324
325
            if ($maxOneRef && count($entity) > 1) {
326
                throw new \InvalidArgumentException($this->getSelfName() . ' does not support setting references for multiple ' . $this->getCollectionName($entity) . 's');
327 13
            }
328
            if ($minOneRef && count($entity) == 0) {
329 13
                throw new \InvalidArgumentException($this->getSelfName() . ' does not support setting references for no ' . $this->getCollectionName($entity) . 's');
330 13
            }
331
        }
332
    }
333 13
334
    /**
335
     * @param array $step
336 1
     * @return string
337
     */
338 1
    protected function getReferencesType($step)
339 1
    {
340 1
        return isset($step->dsl['references_type']) ? $step->dsl['references_type'] : self::REFERENCE_TYPE_SCALAR;
341
    }
342 1
343
    /**
344
     * @param array $step
345 1
     * @return bool
346
     */
347 1
    protected function allowEmptyReferences($step)
348 1
    {
349 1
        if (isset($step->dsl['references_type']) && $step->dsl['references_type'] == self::REFERENCE_TYPE_ARRAY &&
350
            isset($step->dsl['references_allow_empty']) && $step->dsl['references_allow_empty'] == true
351 1
        )
352
            return true;
353
        return false;
354
    }
355
356
    protected function getSelfName()
357
    {
358 19
        $className = get_class($this);
359
        $array = explode('\\', $className);
360 19
        $className = end($array);
361 19
        // CamelCase to Camel Case using negative look-behind in regexp
362 19
        return preg_replace('/(?<!^)[A-Z]/', ' $0', $className);
363
    }
364 18
365
    protected function getCollectionName($collection)
366 19
    {
367
        $className = get_class($collection);
368
        $array = explode('\\', $className);
369
        $className = str_replace('Collection', '', end($array));
370
        // CamelCase to snake case using negative look-behind in regexp
371
        return strtolower(preg_replace('/(?<!^)[A-Z]/', ' $0', $className));
372
    }
373
374
    /**
375
     * Courtesy code to avoid reimplementing it in every subclass
376
     * @todo will be moved into the reference resolver classes
377
     */
378
    protected function resolveReferencesRecursively($match)
379
    {
380
        if (is_array($match)) {
381
            foreach ($match as $condition => $values) {
382
                $match[$condition] = $this->resolveReferencesRecursively($values);
383
            }
384
            return $match;
385
        } else {
386
            return $this->referenceResolver->resolveReference($match);
387
        }
388
    }
389
}
390