Completed
Push — master ( e72073...b2030e )
by Gaetano
08:10
created

LocationManager::setReferences()   D

Complexity

Conditions 10
Paths 16

Size

Total Lines 35
Code Lines 24

Duplication

Lines 35
Ratio 100 %

Code Coverage

Tests 10
CRAP Score 10.4632

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 35
loc 35
ccs 10
cts 12
cp 0.8333
rs 4.8196
cc 10
eloc 24
nc 16
nop 1
crap 10.4632

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use eZ\Publish\API\Repository\Values\Content\Location;
6
use Kaliop\eZMigrationBundle\API\Collection\ContentCollection;
7
use Kaliop\eZMigrationBundle\API\Collection\LocationCollection;
8
use Kaliop\eZMigrationBundle\Core\Matcher\ContentMatcher;
9
use Kaliop\eZMigrationBundle\Core\Matcher\LocationMatcher;
10
11
class LocationManager extends RepositoryExecutor
12
{
13
    protected $supportedStepTypes = array('location');
14
15
    protected $contentMatcher;
16
    protected $locationMatcher;
17
18 20
    public function __construct(ContentMatcher $contentMatcher, LocationMatcher $locationMatcher)
19
    {
20 20
        $this->contentMatcher = $contentMatcher;
21 20
        $this->locationMatcher = $locationMatcher;
22 20
    }
23
24
    /**
25
     * Method to handle the create operation of the migration instructions
26
     */
27 1
    protected function create()
28
    {
29 1
        $locationService = $this->repository->getLocationService();
30
31 1
        if (!isset($this->dsl['parent_location_id'])) {
32
            throw new \Exception('Missing parent location id. This is required to create the new location.');
33
        }
34 1
        if (!is_array($this->dsl['parent_location_id'])) {
35 1
            $this->dsl['parent_location_id'] = array($this->dsl['parent_location_id']);
36 1
        }
37 1
        foreach ($this->dsl['parent_location_id'] as $id => $parentLocationId) {
38 1
            if ($this->referenceResolver->isReference($parentLocationId)) {
39 1
                $this->dsl['parent_location_id'][$id] = $this->referenceResolver->getReferenceValue($parentLocationId);
40 1
            }
41 1
        }
42
43 1
        $contentCollection = $this->matchContents('create');
44
45 1
        $locations = null;
46 1
        foreach ($contentCollection as $content) {
0 ignored issues
show
Bug introduced by
The expression $contentCollection of type object<Kaliop\eZMigratio...ContentCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
47 1
            $contentInfo = $content->contentInfo;
48
49 1
            foreach ($this->dsl['parent_location_id'] as $parentLocationId) {
0 ignored issues
show
Bug introduced by
The expression $this->dsl['parent_location_id'] of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
50 1
                $locationCreateStruct = $locationService->newLocationCreateStruct($parentLocationId);
51
52 1
                if (isset($this->dsl['is_hidden'])) {
53
                    $locationCreateStruct->hidden = $this->dsl['is_hidden'];
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->dsl['is_hidden'] of type array is incompatible with the declared type boolean of property $hidden.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
54 1
                }
55 1
56 1
                if (isset($this->dsl['priority'])) {
57
                    $locationCreateStruct->priority = $this->dsl['priority'];
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->dsl['priority'] of type array is incompatible with the declared type integer of property $priority.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
58 1
                }
59 1
60
                if (isset($this->dsl['sort_order'])) {
61 1
                    $locationCreateStruct->sortOrder = $this->getSortOrder($this->dsl['sort_order']);
0 ignored issues
show
Documentation introduced by
$this->dsl['sort_order'] is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
62 1
                }
63 1
64
                if (isset($this->dsl['sort_field'])) {
65 1
                    $locationCreateStruct->sortField = $this->getSortField($this->dsl['sort_field']);
66 1
                }
67
68
                $locations[] = $locationService->createLocation($contentInfo, $locationCreateStruct);
69
            }
70
        }
71
72
        $locationCollection = new LocationCollection($locations);
0 ignored issues
show
Documentation introduced by
$locations is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
73
74
        $this->setReferences($locationCollection);
75 1
76
        return $locationCollection;
77 1
    }
78
79 1
    /**
80
     * Updates basic information for a location like priority, sort field and sort order.
81 1
     * Updates the visibility of the location when needed.
82
     * Can move a location and its children to a new parent location or swap two locations.
83
     *
84
     * @todo add support for flexible matchers
85 1
     */
86
    protected function update()
87
    {
88
        $locationService = $this->repository->getLocationService();
89 1
90
        $locationCollection = $this->matchLocations('update');
91
92
        if (count($locationCollection) > 1 && array_key_exists('references', $this->dsl)) {
93
            throw new \Exception("Can not execute Location update because multiple contents match, and a references section is specified in the dsl. References can be set when only 1 content matches");
94
        }
95
96
        if (count($locationCollection) > 1 && array_key_exists('swap_with_location', $this->dsl)) {
97
            throw new \Exception("Can not execute Location update because multiple contents match, and a swap_with_location is specified in the dsl.");
98 1
        }
99
100
        if (isset($this->dsl['swap_with_location']) && isset($this->dsl['parent_location_id'])) {
101 1
            throw new \Exception('Cannot move location to a new parent and swap location with another location at the same time.');
102 1
        }
103 1
104 1
        /*$locationId = $this->dsl['location_id'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
105 1
        if ($this->referenceResolver->isReference($locationId)) {
106 1
            $locationId = $this->referenceResolver->getReferenceValue($locationId);
107
        }*/
108 1
109 1
        foreach ($locationCollection as $key => $location) {
0 ignored issues
show
Bug introduced by
The expression $locationCollection of type object<Kaliop\eZMigratio...ocationCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
110 1
            //$location = $locationService->loadLocation($locationId);
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
111
112 1
            if (array_key_exists('priority', $this->dsl)
113
                || array_key_exists('sort_field', $this->dsl)
114
                || array_key_exists('sort_order', $this->dsl)
115
                || array_key_exists('remote_id', $this->dsl)
116 1
            ) {
117 1
                $locationUpdateStruct = $locationService->newLocationUpdateStruct();
118 1
119
                    if (isset($this->dsl['priority'])) {
120 1
                        $locationUpdateStruct->priority = $this->dsl['priority'];
121
                    }
122
123
                    if (isset($this->dsl['sort_field'])) {
124 1
                        $locationUpdateStruct->sortField = $this->getSortField($this->dsl['sort_field'], $location->sortField);
125 1
                    }
126
127
                    if (isset($this->dsl['sort_order'])) {
128 1
                        $locationUpdateStruct->sortOrder = $this->getSortOrder($this->dsl['sort_order'], $location->sortOrder);
129 1
                    }
130 1
131 1
                    if (isset($this->dsl['remote_id'])) {
132 1
                        $locationUpdateStruct->remoteId = $this->dsl['remote_id'];
133
                    }
134 1
135
                $location = $locationService->updateLocation($location, $locationUpdateStruct);
136
            }
137 1
138
            // Check if visibility needs to be updated
139 1
            if (isset($this->dsl['is_hidden'])) {
140 1
                if ($this->dsl['is_hidden']) {
141 1
                    $location = $locationService->hideLocation($location);
142 1
                } else {
143 1
                    $location = $locationService->unhideLocation($location);
144 1
                }
145 1
            }
146
147
            // Move or swap location
148
            if (isset($this->dsl['parent_location_id'])) {
149
                // Move the location and all it's children to a new parent
150
                $parentLocationId = $this->dsl['parent_location_id'];
151
                if ($this->referenceResolver->isReference($parentLocationId)) {
152
                    $parentLocationId = $this->referenceResolver->getReferenceValue($parentLocationId);
153
                }
154
                $newParentLocation = $locationService->loadLocation($parentLocationId);
155
                $locationService->moveSubtree($location, $newParentLocation);
156 1 View Code Duplication
            } elseif (isset($this->dsl['swap_with_location'])) {
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...
157 1
                //Swap locations
158 1
                $swapLocationId = $this->dsl['swap_with_location'];
159
                if ($this->referenceResolver->isReference($swapLocationId)) {
160
                    $swapLocationId = $this->referenceResolver->getReferenceValue($swapLocationId);
161
                }
162
                $locationToSwap = $locationService->loadLocation($swapLocationId);
163
164
                $locationService->swapLocation($location, $locationToSwap);
165
            }
166
167
            $locationCollection[$key] = $location;
168
        }
169
170
        $this->setReferences($locationCollection);
0 ignored issues
show
Bug introduced by
It seems like $locationCollection defined by $this->matchLocations('update') on line 90 can be null; however, Kaliop\eZMigrationBundle...anager::setReferences() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
171
172
        return $locationCollection;
173
    }
174
175
    /**
176
     * Delete locations identified by their ids.
177
     *
178
     * @todo add support for flexible matchers
179
     */
180
    protected function delete()
181
    {
182 1
        $locationService = $this->repository->getLocationService();
183
184 1
        $locationCollection = $this->matchLocations('delete');
185 1
186
        foreach ($locationCollection as $location) {
0 ignored issues
show
Bug introduced by
The expression $locationCollection of type object<Kaliop\eZMigratio...ocationCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
187
            //$location = $locationService->loadLocation($locationId);
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
188
            $locationService->deleteLocation($location);
189 1
        }
190 1
191 1
        return $locationCollection;
192
    }
193 1
194
    /**
195
     * @param string $action
196 1
     * @return ContentCollection
197 1
     * @throws \Exception
198
     */
199 View Code Duplication
    protected function matchLocations($action)
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...
200
    {
201
        if (!isset($this->dsl['location_id'])&& !isset($this->dsl['match'])) {
202
            throw new \Exception("The ID or a Match Condition is required to $action a location.");
203
        }
204 1
205 1
        // Backwards compat
206 1
        if (!isset($this->dsl['match'])) {
207 1
            $this->dsl['match'] = array('location_id' => $this->dsl['location_id']);
208 1
        }
209
210 1
        $match = $this->dsl['match'];
211
212
        // convert the references passed in the match
213
        foreach ($match as $condition => $values) {
214
            if (is_array($values)) {
215
                foreach ($values as $position => $value) {
216
                    if ($this->referenceResolver->isReference($value)) {
217
                        $match[$condition][$position] = $this->referenceResolver->getReferenceValue($value);
218
                    }
219
                }
220 1
            } else {
221
                if ($this->referenceResolver->isReference($values)) {
222 1
                    $match[$condition] = $this->referenceResolver->getReferenceValue($values);
223
                }
224
            }
225
        }
226
227 1
        return $this->locationMatcher->match($match);
228
    }
229
230
    /**
231
     * NB: weirdly enough, it returns contents, not locations
232
     *
233
     * @param string $action
234
     * @return ContentCollection
235 1
     * @throws \Exception
236
     */
237 View Code Duplication
    protected function matchContents($action)
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...
238 1
    {
239 1
        if (!isset($this->dsl['object_id']) && !isset($this->dsl['remote_id']) && !isset($this->dsl['match'])) {
240
            throw new \Exception("The ID or remote ID of an object or a Match Condition is required to $action a new location.");
241
        }
242
243
        // Backwards compat
244
        if (!isset($this->dsl['match'])) {
245
            if (isset($this->dsl['object_id'])) {
246 1
                $this->dsl['match'] = array('content_id' => $this->dsl['object_id']);
247 1
            } elseif (isset($this->dsl['remote_id'])) {
248 1
                $this->dsl['match'] = array('content_remote_id' => $this->dsl['remote_id']);
249
            }
250 1
        }
251
252 1
        $match = $this->dsl['match'];
253
254
        // convert the references passed in the match
255 1
        foreach ($match as $condition => $values) {
256 1
            if (is_array($values)) {
257 1
                foreach ($values as $position => $value) {
258
                    if ($this->referenceResolver->isReference($value)) {
259 1
                        $match[$condition][$position] = $this->referenceResolver->getReferenceValue($value);
260
                    }
261
                }
262
            } else {
263 1
                if ($this->referenceResolver->isReference($values)) {
264 1
                    $match[$condition] = $this->referenceResolver->getReferenceValue($values);
265
                }
266 1
            }
267
        }
268 1
269 1
        return $this->contentMatcher->matchContent($match);
270
    }
271 1
272
    protected function getSortField($newValue, $currentValue = null)
273
    {
274
        $sortField = null;
275
276
        if (!is_null($currentValue)) {
277
            $sortField = $currentValue;
278
        }
279
280
        if ($newValue !== null) {
281
            $sortFieldId = "SORT_FIELD_" . strtoupper($this->dsl['sort_field']);
282
283
            $ref = new \ReflectionClass('eZ\Publish\API\Repository\Values\Content\Location');
284 1
285 1
            $sortField = $ref->getConstant($sortFieldId);
286 1
        }
287 1
288 1
        return $sortField;
289 1
    }
290
291 1
    /**
292 1
     * Get the sort order based on the current value and the value in the DSL definition.
293 1
     *
294 1
     * @see \eZ\Publish\API\Repository\Values\Content\Location::SORT_ORDER_*
295 1
     *
296
     * @param int $newValue
297 1
     * @param int $currentValue
298
     * @return int
299 1
     */
300
    protected function getSortOrder($newValue, $currentValue = null)
301
    {
302
        $sortOrder = null;
303
304
        if (!is_null($currentValue)) {
305
            $sortOrder = $currentValue;
306
        }
307
308
        if ($newValue !== null) {
309
            if (strtoupper($this->dsl['sort_order']) === 'ASC') {
310
                $sortOrder = Location::SORT_ORDER_ASC;
311 1
            } else {
312
                $sortOrder = Location::SORT_ORDER_DESC;
313 1
            }
314 1
        }
315
316
        return $sortOrder;
317 1
    }
318 1
319
    /**
320
     * Sets references to object attributes
321 1
     *
322 1
     * The Location Manager currently supports setting references to location id.
323
     *
324 1
     * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute.
325 1
     * @param \eZ\Publish\API\Repository\Values\Content\Location|LocationCollection $location
326 1
     * @return boolean
327 1
     */
328 1 View Code Duplication
    protected function setReferences($location)
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...
329 1
    {
330 1
        if (!array_key_exists('references', $this->dsl)) {
331 1
            return false;
332 1
        }
333 1
334
        if ($location instanceof LocationCollection) {
335
            if (count($location) > 1) {
336 1
                throw new \InvalidArgumentException('Location Manager does not support setting references for creating/updating of multiple locations');
337
            }
338 1
            $location = reset($location);
339 1
        }
340
341 1
        foreach ($this->dsl['references'] as $reference) {
342
            switch ($reference['attribute']) {
343
                case 'location_id':
344
                case 'id':
345
                    $value = $location->id;
346
                    break;
347
                case 'remote_id':
348
                case 'location_remote_id':
349
                    $value = $location->remoteId;
350
                    break;
351
                case 'path':
352
                    $value = $location->pathString;
353
                    break;
354
                default:
355
                    throw new \InvalidArgumentException('Location Manager does not support setting references for attribute ' . $reference['attribute']);
356
            }
357
358
            $this->referenceResolver->addReference($reference['identifier'], $value);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Kaliop\eZMigrationBundle...erenceResolverInterface as the method addReference() does only exist in the following implementations of said interface: Kaliop\eZMigrationBundle...CustomReferenceResolver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
359
        }
360
361
        return true;
362
    }
363
}
364