Completed
Push — master ( 956196...175692 )
by Gaetano
09:59
created

LocationManager::matchContents()   C

Complexity

Conditions 12
Paths 21

Size

Total Lines 34
Code Lines 18

Duplication

Lines 34
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 34
loc 34
rs 5.1612
cc 12
eloc 18
nc 21
nop 1

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\Core\Matcher\ContentMatcher;
8
9
class LocationManager extends RepositoryExecutor
10
{
11
    protected $supportedStepTypes = array('location');
12
13
    protected $contentMatcher;
14
15
    public function __construct(ContentMatcher $contentMatcher)
16
    {
17
        $this->contentMatcher = $contentMatcher;
18
    }
19
20
    /**
21
     * NB: weirdly enough, it returns contents, not locations
22
     *
23
     * @param string $action
24
     * @return ContentCollection
25
     * @throws \Exception
26
     */
27 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...
28
    {
29
        if (!isset($this->dsl['object_id']) && !isset($this->dsl['remote_id']) && !isset($this->dsl['match'])) {
30
            throw new \Exception("The ID or remote ID of an object or a Match Condition is required to $action a new location.");
31
        }
32
33
        // Backwards compat
34
        if (!isset($this->dsl['match'])) {
35
            if (isset($this->dsl['object_id'])) {
36
                $this->dsl['match'] = array('content_id' => $this->dsl['object_id']);
37
            } elseif (isset($this->dsl['remote_id'])) {
38
                $this->dsl['match'] = array('content_remote_id' => $this->dsl['remote_id']);
39
            }
40
        }
41
42
        $match = $this->dsl['match'];
43
44
        // convert the references passed in the match
45
        foreach ($match as $condition => $values) {
46
            if (is_array($values)) {
47
                foreach ($values as $position => $value) {
48
                    if ($this->referenceResolver->isReference($value)) {
49
                        $match[$condition][$position] = $this->referenceResolver->getReferenceValue($value);
50
                    }
51
                }
52
            } else {
53
                if ($this->referenceResolver->isReference($values)) {
54
                    $match[$condition] = $this->referenceResolver->getReferenceValue($values);
55
                }
56
            }
57
        }
58
59
        return $this->contentMatcher->matchContent($match);
60
    }
61
62
    /**
63
     * Method to handle the create operation of the migration instructions
64
     */
65
    protected function create()
66
    {
67
        $this->loginUser();
68
69
        $locationService = $this->repository->getLocationService();
70
71
        if (!isset($this->dsl['parent_location_id'])) {
72
            throw new \Exception('Missing parent location id. This is required to create the new location.');
73
        }
74
        if (!is_array($this->dsl['parent_location_id'])) {
75
            $this->dsl['parent_location_id'] = array($this->dsl['parent_location_id']);
76
        }
77
        foreach ($this->dsl['parent_location_id'] as $id => $parentLocationId) {
78
            if ($this->referenceResolver->isReference($parentLocationId)) {
79
                $this->dsl['parent_location_id'][$id] = $this->referenceResolver->getReferenceValue($parentLocationId);
80
            }
81
        }
82
83
        $contentCollection = $this->matchContents('create');
84
85
        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...
86
            $contentInfo = $content->contentInfo;
87
88
            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...
89
                $locationCreateStruct = $locationService->newLocationCreateStruct($parentLocationId);
90
91
                $locationCreateStruct->hidden = isset($this->dsl['is_hidden']) ?: false;
92
93
                if (isset($this->dsl['priority'])) {
94
                    $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...
95
                }
96
97
                $locationCreateStruct->sortOrder = $this->getSortOrder();
98
                $locationCreateStruct->sortField = $this->getSortField();
99
100
                $locationService->createLocation($contentInfo, $locationCreateStruct);
101
            }
102
        }
103
    }
104
105
    /**
106
     * Method to handle the update operation of the migration instructions
107
     *
108
     * Updates basic information for a location like priority, sort field and sort order.
109
     * Updates the visibility of the location when needed
110
     *
111
     * Can move a location and it's children to a new parent location or swap two locations
112
     */
113
    protected function update()
114
    {
115
        $this->loginUser();
116
117
        $locationService = $this->repository->getLocationService();
118
119
        if (!isset($this->dsl['location_id'])) {
120
            throw new \Exception('No location set for update.');
121
        }
122
123
        if (isset($this->dsl['swap_with_location']) && isset($this->dsl['patent_location_id'])) {
124
            throw new \Exception('Cannot move location to a new parent and swap location with another location at the same time.');
125
        }
126
127
        $locationId = $this->dsl['location_id'];
128
        if ($this->referenceResolver->isReference($locationId)) {
129
            $locationId = $this->referenceResolver->getReferenceValue($locationId);
130
        }
131
132
        $location = $locationService->loadLocation($locationId);
133
134
        if (array_key_exists('priority', $this->dsl)
135
            || array_key_exists('sort_field', $this->dsl)
136
            || array_key_exists('sort_order', $this->dsl)
137
        ) {
138
            $locationUpdateStruct = $locationService->newLocationUpdateStruct();
139
140
            if (isset($this->dsl['priority'])) {
141
                $locationUpdateStruct->priority = $this->dsl['priority'];
142
            }
143
144
            if (isset($this->dsl['sort_field'])) {
145
                $locationUpdateStruct->sortField = $this->getSortField($location->sortField);
146
            }
147
148
            if (isset($this->dsl['sort_order'])) {
149
                $locationUpdateStruct->sortOrder = $this->getSortOrder($location->sortOrder);
150
            }
151
152
            $locationService->updateLocation($location, $locationUpdateStruct);
153
        }
154
155
        // Check if visibility needs to be updated
156
        if (isset($this->dsl['is_hidden'])) {
157
            if ($this->dsl['is_hidden']) {
158
                $locationService->hideLocation($location);
159
            } else {
160
                $locationService->unhideLocation($location);
161
            }
162
        }
163
164
        // Move or swap location
165
        if (isset($this->dsl['parent_location_id'])) {
166
            // Move the location and all it's children to a new parent
167
            $parentLocationId = $this->dsl['parent_location_id'];
168
            if ($this->referenceResolver->isReference($parentLocationId)) {
169
                $parentLocationId = $this->referenceResolver->getReferenceValue($parentLocationId);
170
            }
171
            $newParentLocation = $locationService->loadLocation($parentLocationId);
172
            $locationService->moveSubtree($location, $newParentLocation);
173 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...
174
            //Swap locations
175
            $swapLocationId = $this->dsl['swap_with_location'];
176
            if ($this->referenceResolver->isReference($swapLocationId)) {
177
                $swapLocationId = $this->referenceResolver->getReferenceValue($swapLocationId);
178
            }
179
            $locationToSwap = $locationService->loadLocation($swapLocationId);
180
181
            $locationService->swapLocation($location, $locationToSwap);
182
        }
183
184
        $this->setReferences($location);
185
    }
186
187
    /**
188
     * Method to handle the delete operation of the migration instructions
189
     *
190
     * Delete locations identified by their ids.
191
     */
192
    protected function delete()
193
    {
194
        $this->loginUser();
195
196
        $locationService = $this->repository->getLocationService();
197
198
        if (!isset($this->dsl['location_id'])) {
199
            throw new \Exception('No location provided for deletion');
200
        }
201
202
        if (!is_array($this->dsl['location_id'])) {
203
            $this->dsl['location_id'] = array($this->dsl['location_id']);
204
        }
205
206
        foreach ($this->dsl['location_id'] as $locationId) {
207
            $location = $locationService->loadLocation($locationId);
208
            $locationService->deleteLocation($location);
209
        }
210
    }
211
212
    protected function getSortField($currentValue = null)
213
    {
214
        $sortField = Location::SORT_FIELD_PUBLISHED;
215
216
        if (!is_null($currentValue)) {
217
            $sortField = $currentValue;
218
        }
219
220
        if (isset($this->dsl['sort_field'])) {
221
            $sortFieldId = "SORT_FIELD_" . strtoupper($this->dsl['sort_field']);
222
223
            $ref = new \ReflectionClass('eZ\Publish\API\Repository\Values\Content\Location');
224
225
            $sortField = $ref->getConstant($sortFieldId);
226
        }
227
228
        return $sortField;
229
    }
230
231
    /**
232
     * Get the sort order based on the current value and the value in the DSL definition.
233
     *
234
     * If no current value is set and there is no value in the DSL it will default to Location::SORT_ORDER_ASC
235
     *
236
     * @see \eZ\Publish\API\Repository\Values\Content\Location::SORT_ORDER_*
237
     *
238
     * @param int $currentValue
239
     * @return int
240
     */
241
    protected function getSortOrder($currentValue = null)
242
    {
243
        $sortOrder = Location::SORT_ORDER_ASC;
244
        if (!is_null($currentValue)) {
245
            $sortOrder = $currentValue;
246
        }
247
248
        if (isset($this->dsl['sort_order'])) {
249
            if (strtoupper($this->dsl['sort_order']) === 'ASC') {
250
                $sortOrder = Location::SORT_ORDER_ASC;
251
            } else {
252
                $sortOrder = Location::SORT_ORDER_DESC;
253
            }
254
        }
255
256
        return $sortOrder;
257
    }
258
259
    /**
260
     * Sets references to object attributes
261
     *
262
     * The Location Manager currently supports setting references to location id.
263
     *
264
     * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute.
265
     * @param \eZ\Publish\API\Repository\Values\Content\Location $location
266
     * @return boolean
267
     */
268 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...
269
    {
270
        if (!array_key_exists('references', $this->dsl)) {
271
            return false;
272
        }
273
274
        foreach ($this->dsl['references'] as $reference) {
275
            switch ($reference['attribute']) {
276
                case 'location_id':
277
                case 'id':
278
                    $value = $location->id;
279
                    break;
280
                default:
281
                    throw new \InvalidArgumentException('Location Manager does not support setting references for attribute ' . $reference['attribute']);
282
            }
283
284
            $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...
285
        }
286
287
        return true;
288
    }
289
}
290