Completed
Push — master ( 09cee1...75eab4 )
by Gaetano
06:21
created

RepositoryExecutor   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 336
Duplicated Lines 7.14 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 6
dl 24
loc 336
ccs 0
cts 115
cp 0
rs 8.6206
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setRepository() 0 4 1
A setConfigResolver() 0 4 1
A setReferenceResolver() 0 4 1
B execute() 0 37 5
getReferencesValues() 0 1 ?
A getLanguageCode() 0 4 2
A getLanguageCodeFromContext() 0 13 4
A getUserContentType() 0 4 2
A getUserContentTypeFromContext() 0 4 2
A getAdminUserIdentifierFromContext() 0 8 2
C setReferences() 7 49 10
B setReferencesCommon() 0 23 4
A insureSingleEntity() 0 10 2
B insureEntityCountCompatibilty() 6 15 7
A areReferencesMultivalued() 0 4 2
A getSelfName() 0 8 1
A getCollectionName() 0 8 1
A resolveReferencesRecursively() 11 11 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like RepositoryExecutor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RepositoryExecutor, and based on these observations, apply Extract Interface, too.

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;
18
    use IgnorableStepExecutorTrait;
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
33
    /**
34
     * @var array $dsl The parsed DSL instruction array
35
     */
36
    //protected $dsl;
37
38
    /** @var array $context The context (configuration) for the execution of the current step */
39
    //protected $context;
40
41
    /**
42
     * The eZ Publish 5 API repository.
43
     *
44
     * @var \eZ\Publish\API\Repository\Repository
45
     */
46
    protected $repository;
47
48
    protected $configResolver;
49
50
    /** @var ReferenceResolverBagInterface $referenceResolver */
51
    protected $referenceResolver;
52
53
    // to redefine in subclasses if they don't support all methods, or if they support more...
54
    protected $supportedActions = array(
55
        'create', 'update', 'delete'
56
    );
57
58
    public function setRepository(Repository $repository)
59
    {
60
        $this->repository = $repository;
61
    }
62
63
    public function setConfigResolver(ConfigResolverInterface $configResolver)
64
    {
65
        $this->configResolver = $configResolver;
66
    }
67
68
    public function setReferenceResolver(ReferenceResolverBagInterface $referenceResolver)
69
    {
70
        $this->referenceResolver = $referenceResolver;
71
    }
72
73
    public function execute(MigrationStep $step)
74
    {
75
        // base checks
76
        parent::execute($step);
77
78
        if (!isset($step->dsl['mode'])) {
79
            throw new \Exception("Invalid step definition: missing 'mode'");
80
        }
81
82
        // q: should we convert snake_case to camelCase ?
83
        $action = $step->dsl['mode'];
84
85
        if (!in_array($action, $this->supportedActions)) {
86
            throw new \Exception("Invalid step definition: value '$action' is not allowed for 'mode'");
87
        }
88
89
        if (method_exists($this, $action)) {
90
91
            $this->skipStepIfNeeded($step);
92
93
            $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($step->context));
94
95
            try {
96
                $output = $this->$action($step);
97
            } catch (\Exception $e) {
98
                $this->loginUser($previousUserId);
99
                throw $e;
100
            }
101
102
            // reset the environment as much as possible as we had found it before the migration
103
            $this->loginUser($previousUserId);
104
105
            return $output;
106
        } else {
107
            throw new \Exception("Invalid step definition: value '$action' is not a method of " . get_class($this));
108
        }
109
    }
110
111
    /**
112
     * Method that each executor (subclass) has to implement.
113
     *
114
     * It is used to get values for references based on the DSL instructions executed in the current step, for later steps to reuse.
115
     *
116
     * @throws \InvalidArgumentException when trying to set a reference to an unsupported attribute.
117
     * @param $object a sinle element to extract reference values from
118
     * @param array $referencesDefinitionsthe definitions of the references to extract
0 ignored issues
show
Documentation introduced by
There is no parameter named $referencesDefinitionsthe. Did you maybe mean $referencesDefinitions?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
119
     * @return array key: the reference name (taken from $referencesDefinitions[n]['identifier'], value: the ref. value
120
     */
121
    abstract protected function getReferencesValues($object, $referencesDefinitions);
122
123
    /**
124
     * @param MigrationStep $step
125
     * @return string
126
     */
127
    protected function getLanguageCode($step)
128
    {
129
        return isset($step->dsl['lang']) ? $step->dsl['lang'] : $this->getLanguageCodeFromContext($step->context);
130
    }
131
132
    /**
133
     * @param array|null $context
134
     * @return string
135
     */
136
    protected function getLanguageCodeFromContext($context)
137
    {
138
        if (is_array($context) && isset($context['defaultLanguageCode'])) {
139
            return $context['defaultLanguageCode'];
140
        }
141
142
        if ($this->configResolver) {
143
            $locales = $this->configResolver->getParameter('languages');
144
            return reset($locales);
145
        }
146
147
        return self::DEFAULT_LANGUAGE_CODE;
148
    }
149
150
    /**
151
     * @param MigrationStep $step
152
     * @return string
153
     */
154
    protected function getUserContentType($step)
155
    {
156
        return isset($step->dsl['user_content_type']) ? $step->dsl['user_content_type'] : $this->getUserContentTypeFromContext($step->context);
157
    }
158
159
    /**
160
     * @param array $context
161
     * @return string
162
     */
163
    protected function getUserContentTypeFromContext($context)
164
    {
165
        return isset($context['userContentType']) ? $context['userContentType'] : self::USER_CONTENT_TYPE;
166
    }
167
168
    /**
169
     * @param array $context we have to return FALSE if that is set as adminUserLogin, whereas if NULL is set, we return the default admin
170
     * @return int|string|false
171
     */
172
    protected function getAdminUserIdentifierFromContext($context)
173
    {
174
        if (isset($context['adminUserLogin'])) {
175
            return $context['adminUserLogin'];
176
        }
177
178
        return self::ADMIN_USER_ID;
179
    }
180
181
    /**
182
     * Sets references to certain content attributes.
183
     *
184
     * @param \Object|AbstractCollectionCollection $item
185
     * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute
186
     * @return boolean
187
     *
188
     * @todo add support for other attributes... ?
189
     */
190
    protected function setReferences($item, $step)
191
    {
192
        if (!array_key_exists('references', $step->dsl)) {
193
            return false;
194
        }
195
196
        $referencesDefs = $this->setReferencesCommon($content, $step->dsl['references']);
0 ignored issues
show
Bug introduced by
The variable $content does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
197
198
        $this->insureEntityCountCompatibilty($content, $referencesDefs);
199
200
        $multivalued = $this->areReferencesMultivalued($referencesDefs);
201
202
        if ($item instanceof AbstractCollection) {
203
            $items = $item;
204
        } else {
205
            $items = array($item);
206
        }
207
208
        if (isset($referencesDefs['multivalued'])) {
209
            unset($referencesDefs['multivalued']);
210
        }
211
212
        $referencesValues = array();
213
        foreach ($items as $item) {
214
            $itemReferencesValues = $this->getReferencesValues($item, $referencesDefs);
215
            if (!$multivalued) {
216
                $referencesValues = $itemReferencesValues;
217
            } else {
218
                foreach ($itemReferencesValues as $refName => $refValue) {
219
                    if (!isset($referencesValues[$refName])) {
220
                        $referencesValues[$refName] = array($refValue);
221
                    } else {
222
                        $referencesValues[$refName][] = $refValue;
223
                    }
224
                }
225
            }
226
        }
227
228 View Code Duplication
        foreach($referencesDefs as $reference) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
229
            $overwrite = false;
230
            if (isset($reference['overwrite'])) {
231
                $overwrite = $reference['overwrite'];
232
            }
233
            $this->referenceResolver->addReference($reference['identifier'], $referencesValues[$reference['identifier']], $overwrite);
234
        }
235
236
237
        return true;
238
    }
239
240
    /**
241
     * @param mixed $entity
242
     * @param array $referencesDefinition
243
     * @return array the same as $referencesDefinition, with the references already treated having been removed
244
     */
245
    protected function setReferencesCommon($entity, $referencesDefinition)
246
    {
247
        // allow setting *some* refs even when we have 0 or N matches
248
        foreach ($referencesDefinition as $key => $reference) {
249
            switch($reference['attribute']) {
250
251
                case 'count':
252
                    $value = count($entity);
253
                    $overwrite = false;
254
                    if (isset($reference['overwrite'])) {
255
                        $overwrite = $reference['overwrite'];
256
                    }
257
                    $this->referenceResolver->addReference($reference['identifier'], $value, $overwrite);
258
                    unset($referencesDefinition[$key]);
259
                    break;
260
261
                default:
262
                    // do nothing
263
            }
264
        }
265
266
        return $referencesDefinition;
267
    }
268
269
    /**
270
     * Verifies compatibility between the definition of the refences to be set and the data set to extarct them from,
271
     * and returns a single entity
272
     *
273
     * @param AbstractCollection|mixed $entity
274
     * @param array $referencesDefinition
275
     * @return AbstractCollection|mixed
276
     */
277
    protected function insureSingleEntity($entity, $referencesDefinition)
278
    {
279
        $this->insureEntityCountCompatibilty($entity, $referencesDefinition);
280
281
        if ($entity instanceof AbstractCollection) {
282
            return reset($entity);
283
        }
284
285
        return $entity;
286
    }
287
288
    /**
289
     * Verifies compatibility between the definition of the refences to be set and the data set to extract them from.
290
     * Nb: for multivalued refs, we assume that the users always expect at least one value
291
     * @param AbstractCollection|mixed $entity
292
     * @param array $referencesDefinition
293
     * @return void throws when incompatibiliy is found
294
     * @todo should we allow to be passed in plain arrays as well ?
295
     */
296
    protected function insureEntityCountCompatibilty($entity, $referencesDefinition)
297
    {
298
        if ($entity instanceof AbstractCollection) {
299
300
            $minOneRef = count($referencesDefinition) > 0;
301
            $maxOneRef = count($referencesDefinition) > 0 && ! $this->areReferencesMultivalued($referencesDefinition);
302
303 View Code Duplication
            if ($maxOneRef && count($entity) > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
304
                throw new \InvalidArgumentException($this->getSelfName() . ' does not support setting references for multiple ' . $this->getCollectionName($entity) . 's');
305
            }
306 View Code Duplication
            if ($minOneRef && count($entity) == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
307
                throw new \InvalidArgumentException($this->getSelfName() . ' does not support setting references for no ' . $this->getCollectionName($entity) . 's');
308
            }
309
        }
310
    }
311
312
    protected function areReferencesMultivalued($referencesDefinition)
313
    {
314
        return isset($referencesDefinition['multivalued']) && $referenceDefinition['multivalued'] == 'enabled';
0 ignored issues
show
Bug introduced by
The variable $referenceDefinition does not exist. Did you mean $referencesDefinition?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
315
    }
316
317
    protected function getSelfName()
318
    {
319
        $className = get_class($this);
320
        $array = explode('\\', $className);
321
        $className = end($array);
322
        // CamelCase to Camel Case using negative look-behind in regexp
323
        return preg_replace('/(?<!^)[A-Z]/', ' $0', $className);
324
    }
325
326
    protected function getCollectionName($collection)
327
    {
328
        $className = get_class($collection);
329
        $array = explode('\\', $className);
330
        $className = str_replace('Collection', '', end($array));
331
        // CamelCase to snake case using negative look-behind in regexp
332
        return strtolower(preg_replace('/(?<!^)[A-Z]/', ' $0', $className));
333
    }
334
335
    /**
336
     * Courtesy code to avoid reimplementing it in every subclass
337
     * @todo will be moved into the reference resolver classes
338
     */
339 View Code Duplication
    protected function resolveReferencesRecursively($match)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340
    {
341
        if (is_array($match)) {
342
            foreach ($match as $condition => $values) {
343
                $match[$condition] = $this->resolveReferencesRecursively($values);
344
            }
345
            return $match;
346
        } else {
347
            return $this->referenceResolver->resolveReference($match);
348
        }
349
    }
350
}
351