LocationManager   F
last analyzed

Complexity

Total Complexity 101

Size/Duplication

Total Lines 492
Duplicated Lines 0 %

Test Coverage

Coverage 87.1%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 244
c 1
b 0
f 0
dl 0
loc 492
ccs 216
cts 248
cp 0.871
rs 2
wmc 101

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getSortOrder() 0 9 2
A matchLocationByKey() 0 3 1
A getSortField() 0 9 2
A __construct() 0 5 1
A load() 0 9 1
D getReferencesValues() 0 91 28
C matchContents() 0 33 13
A setMainLocation() 0 6 1
F create() 0 77 17
A trash() 0 15 2
F update() 0 110 23
B matchLocations() 0 23 8
A delete() 0 15 2

How to fix   Complexity   

Complex Class

Complex classes like LocationManager 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.

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 LocationManager, 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\Values\Content\Location;
6
use Kaliop\eZMigrationBundle\API\Collection\ContentCollection;
7
use Kaliop\eZMigrationBundle\API\Collection\LocationCollection;
8
use Kaliop\eZMigrationBundle\API\Exception\InvalidStepDefinitionException;
9
use Kaliop\eZMigrationBundle\API\Exception\MigrationBundleException;
10
use Kaliop\eZMigrationBundle\Core\Matcher\ContentMatcher;
11
use Kaliop\eZMigrationBundle\Core\Matcher\LocationMatcher;
12
use Kaliop\eZMigrationBundle\Core\Helper\SortConverter;
13
14
/**
15
 * Handles location migrations.
16
 */
17
class LocationManager extends RepositoryExecutor
18
{
19
    protected $supportedStepTypes = array('location');
20
    protected $supportedActions = array('create', 'load', 'update', 'delete', 'trash');
21
22
    protected $contentMatcher;
23
    protected $locationMatcher;
24
    protected $sortConverter;
25
26 149
    public function __construct(ContentMatcher $contentMatcher, LocationMatcher $locationMatcher, SortConverter $sortConverter)
27
    {
28 149
        $this->contentMatcher = $contentMatcher;
29 149
        $this->locationMatcher = $locationMatcher;
30 149
        $this->sortConverter = $sortConverter;
31 149
    }
32
33
    /**
34
     * Method to handle the create operation of the migration instructions
35
     */
36 2
    protected function create($step)
37
    {
38 2
        $locationService = $this->repository->getLocationService();
39
40 2
        if (!isset($step->dsl['parent_location']) && !isset($step->dsl['parent_location_id'])) {
41
            throw new InvalidStepDefinitionException('Missing parent location id. This is required to create the new location.');
42
        }
43
44
        // support legacy tag: parent_location_id
45 2
        if (!isset($step->dsl['parent_location']) && isset($step->dsl['parent_location_id'])) {
46 1
            $parentLocationIds = $step->dsl['parent_location_id'];
47
        } else {
48 2
            $parentLocationIds = $step->dsl['parent_location'];
49
        }
50
51 2
        if (!is_array($parentLocationIds)) {
52 2
            $parentLocationIds = array($parentLocationIds);
53
        }
54
55 2
        if (isset($step->dsl['is_main']) && count($parentLocationIds) > 1) {
56
            throw new InvalidStepDefinitionException('Can not set more than one new location as main.');
57
        }
58
59
        // resolve references and remote ids
60 2
        foreach ($parentLocationIds as $id => $parentLocationId) {
61 2
            $parentLocationId = $this->resolveReference($parentLocationId);
62 2
            $parentLocationIds[$id] = $this->matchLocationByKey($parentLocationId)->id;
63
        }
64
65 2
        $contentCollection = $this->matchContents('create', $step);
66
67 2
        $locations = array();
68 2
        foreach ($contentCollection as $content) {
69 2
            $contentInfo = $content->contentInfo;
70
71 2
            foreach ($parentLocationIds as $parentLocationId) {
72 2
                $locationCreateStruct = $locationService->newLocationCreateStruct($parentLocationId);
73
74 2
                if (isset($step->dsl['is_hidden'])) {
75 1
                    $locationCreateStruct->hidden = $this->resolveReference($step->dsl['is_hidden']);
76
                }
77
78 2
                if (isset($step->dsl['priority'])) {
79 1
                    $locationCreateStruct->priority = $this->resolveReference($step->dsl['priority']);
80
                }
81
82 2
                if (isset($step->dsl['sort_order'])) {
83 1
                    $locationCreateStruct->sortOrder = $this->getSortOrder($this->resolveReference($step->dsl['sort_order']));
84
                }
85
86 2
                if (isset($step->dsl['sort_field'])) {
87 1
                    $locationCreateStruct->sortField = $this->getSortField($this->resolveReference($step->dsl['sort_field']));
88
                }
89
90 2
                if (isset($step->dsl['remote_id'])) {
91
                    $locationCreateStruct->remoteId = $this->resolveReference($step->dsl['remote_id']);
92
                }
93
94 2
                $location = $locationService->createLocation($contentInfo, $locationCreateStruct);
95
96 2
                if (isset($step->dsl['is_main'])) {
97 1
                    $this->setMainLocation($location);
98
                    // we have to reload the location so that correct data can be set as reference
99 1
                    $location = $locationService->loadLocation($location->id);
100
                }
101
102 2
                $locations[] = $location;
103
            }
104
        }
105
106 2
        $locationCollection = new LocationCollection($locations);
107
108 2
        $this->validateResultsCount($locationCollection, $step);
109
110 2
        $this->setReferences($locationCollection, $step);
111
112 2
        return $locationCollection;
113
    }
114
115 3
    protected function load($step)
116
    {
117 3
        $locationCollection = $this->matchLocations('load', $step);
118
119 3
        $this->validateResultsCount($locationCollection, $step);
120
121 3
        $this->setReferences($locationCollection, $step);
122
123 3
        return $locationCollection;
124
    }
125
126
    /**
127
     * Updates information for a location like priority, sort field and sort order.
128
     * Updates the visibility of the location when needed.
129
     * Can move a location and its children to a new parent location or swap two locations.
130
     */
131 2
    protected function update($step)
132
    {
133 2
        $locationService = $this->repository->getLocationService();
134
135 2
        $locationCollection = $this->matchLocations('update', $step);
136
137 2
        $this->validateResultsCount($locationCollection, $step);
138
139 2
        if (count($locationCollection) > 1 && isset($step->dsl['swap_with_location'])) {
140
            throw new MigrationBundleException("Can not execute Location update because multiple locations match, and a swap_with_location is specified in the dsl.");
141
        }
142
143
        // support legacy tag: parent_location_id
144 2
        if (isset($step->dsl['swap_with_location']) && (isset($step->dsl['parent_location']) || isset($step->dsl['parent_location_id']))) {
145
            throw new InvalidStepDefinitionException('Cannot move location to a new parent and swap location with another location at the same time.');
146
        }
147
148 2
        foreach ($locationCollection as $key => $location) {
149
150 2
            $updated = false;
151
152 2
            if (isset($step->dsl['priority'])
153 2
                || isset($step->dsl['sort_field'])
154 2
                || isset($step->dsl['sort_order'])
155 2
                || isset($step->dsl['remote_id'])
156
            ) {
157 1
                $locationUpdateStruct = $locationService->newLocationUpdateStruct();
158
159 1
                if (isset($step->dsl['priority'])) {
160 1
                    $locationUpdateStruct->priority = $this->resolveReference($step->dsl['priority']);
161
                }
162
163 1
                if (isset($step->dsl['sort_field'])) {
164
                    $locationUpdateStruct->sortField = $this->getSortField($this->resolveReference($step->dsl['sort_field']), $location->sortField);
165
                }
166
167 1
                if (isset($step->dsl['sort_order'])) {
168 1
                    $locationUpdateStruct->sortOrder = $this->getSortOrder($this->resolveReference($step->dsl['sort_order']), $location->sortOrder);
169
                }
170
171 1
                if (isset($step->dsl['remote_id'])) {
172
                    $locationUpdateStruct->remoteId = $this->resolveReference($step->dsl['remote_id']);
173
                }
174
175 1
                $location = $locationService->updateLocation($location, $locationUpdateStruct);
176
177 1
                $updated = true;
178
            }
179
180
            // Check if visibility needs to be updated
181 2
            if (isset($step->dsl['is_hidden'])) {
182 1
                if ($step->dsl['is_hidden']) {
183 1
                    $location = $locationService->hideLocation($location);
184
                } else {
185 1
                    $location = $locationService->unhideLocation($location);
186
                }
187
188 1
                $updated = true;
189
            }
190
191
            // Move or swap location
192 2
            if (isset($step->dsl['parent_location']) || isset($step->dsl['parent_location_id'])) {
193
                // Move the location and all its children to a new parent
194 1
                $parentLocationId = isset($step->dsl['parent_location']) ? $step->dsl['parent_location'] : $step->dsl['parent_location_id'];
195 1
                $parentLocationId = $this->resolveReference($parentLocationId);
196
197 1
                $newParentLocation = $this->matchLocationByKey($parentLocationId);
198
199 1
                $locationService->moveSubtree($location, $newParentLocation);
200
201
                // we have to reload the location to be able to set references to the modified data
202 1
                $location = $locationService->loadLocation($location->id);
203
204 1
                $updated = true;
205 2
            } elseif (isset($step->dsl['swap_with_location'])) {
206
                // Swap locations
207
                $swapLocationId = $step->dsl['swap_with_location'];
208
                $swapLocationId = $this->resolveReference($swapLocationId);
209
210
                $locationToSwap = $this->matchLocationByKey($swapLocationId);
211
212
                $locationService->swapLocation($location, $locationToSwap);
213
214
                // we have to reload the location to be able to set references to the modified data
215
                $location = $locationService->loadLocation($location->id);
216
217
                $updated = true;
218
            }
219
220
            // make the location the main one
221 2
            if (isset($step->dsl['is_main'])) {
222 1
                $this->setMainLocation($location);
223
224
                // have to reload the location so that correct data can be set as reference
225 1
                $location = $locationService->loadLocation($location->id);
226
227 1
                $updated = true;
228
            }
229
230 2
            if (!$updated) {
231
                /// @todo check: should we throw before executing changes?
232 1
                throw new InvalidStepDefinitionException("Can not execute Location update because there is nothing to update in the migration step as defined.");
233
            }
234
235 1
            $locationCollection[$key] = $location;
236
        }
237
238 1
        $this->setReferences($locationCollection, $step);
239
240 1
        return $locationCollection;
241
    }
242
243
    /**
244
     * Delete locations
245
     */
246
    protected function delete($step)
247
    {
248
        $locationCollection = $this->matchLocations('delete', $step);
249
250
        $this->validateResultsCount($locationCollection, $step);
251
252
        $this->setReferences($locationCollection, $step);
253
254
        $locationService = $this->repository->getLocationService();
255
256
        foreach ($locationCollection as $location) {
257
            $locationService->deleteLocation($location);
258
        }
259
260
        return $locationCollection;
261
    }
262
263
    /**
264
     * Delete locations sending them to the trash
265
     */
266 1
    protected function trash($step)
267
    {
268 1
        $locationCollection = $this->matchLocations('delete', $step);
269
270 1
        $this->validateResultsCount($locationCollection, $step);
271
272 1
        $this->setReferences($locationCollection, $step);
273
274 1
        $trashService = $this->repository->getTrashService();
275
276 1
        foreach ($locationCollection as $location) {
277 1
            $trashService->trash($location);
278
        }
279
280 1
        return $locationCollection;
281
    }
282
283
    /**
284
     * @param string $action
285
     * @return LocationCollection
286
     * @throws \Exception
287
     */
288 4
    public function matchLocations($action, $step)
289
    {
290 4
        if (!isset($step->dsl['location_id']) && !isset($step->dsl['match'])) {
291
            throw new InvalidStepDefinitionException("The id or a match condition is required to $action a location");
292
        }
293
294
        // Backwards compat
295 4
        if (isset($step->dsl['match'])) {
296 4
            $match = $step->dsl['match'];
297
        } else {
298 1
            $match = array('location_id' => $step->dsl['location_id']);
299
        }
300
301
        // convert the references passed in the match
302 4
        $match = $this->resolveReferencesRecursively($match);
303
304 4
        $offset = isset($step->dsl['match_offset']) ? $this->resolveReference($step->dsl['match_offset']) : 0;
305 4
        $limit = isset($step->dsl['match_limit']) ? $this->resolveReference($step->dsl['match_limit']) : 0;
306 4
        $sort = isset($step->dsl['match_sort']) ? $this->resolveReference($step->dsl['match_sort']) : array();
307
308 4
        $tolerateMisses = isset($step->dsl['match_tolerate_misses']) ? $this->resolveReference($step->dsl['match_tolerate_misses']) : false;
309
310 4
        return $this->locationMatcher->match($match, $sort, $offset, $limit, $tolerateMisses);
311
    }
312
313
    /**
314
     * @param Location $location
315
     * @param array $references the definitions of the references to set
316
     * @throws InvalidStepDefinitionException
317
     * @return array key: the reference names, values: the reference values
318
     */
319 3
    protected function getReferencesValues($location, array $references, $step)
320
    {
321 3
        $refs = array();
322
323 3
        foreach ($references as $key => $reference) {
324
325 2
            $reference = $this->parseReferenceDefinition($key, $reference);
326
327 2
            switch ($reference['attribute']) {
328 2
                case 'location_id':
329 1
                case 'id':
330 2
                    $value = $location->id;
331 2
                    break;
332 1
                case 'remote_id':
333 1
                case 'location_remote_id':
334 1
                    $value = $location->remoteId;
335 1
                    break;
336 1
                case 'always_available':
337 1
                    $value = $location->contentInfo->alwaysAvailable;
338 1
                    break;
339 1
                case 'content_id':
340 1
                    $value = $location->contentId;
341 1
                    break;
342 1
                case 'content_type_id':
343 1
                    $value = $location->contentInfo->contentTypeId;
344 1
                    break;
345 1
                case 'content_type_identifier':
346 1
                    $contentTypeService = $this->repository->getContentTypeService();
347 1
                    $value = $contentTypeService->loadContentType($location->contentInfo->contentTypeId)->identifier;
348 1
                    break;
349 1
                case 'content_remote_id':
350
                    $value = $location->contentInfo->remoteId;
351
                    break;
352 1
                case 'current_version':
353 1
                case 'current_version_no':
354 1
                    $value = $location->contentInfo->currentVersionNo;
355 1
                    break;
356 1
                case 'depth':
357 1
                    $value = $location->depth;
358 1
                    break;
359 1
                case 'is_hidden':
360 1
                    $value = $location->hidden;
361 1
                    break;
362 1
                case 'main_location_id':
363 1
                    $value = $location->contentInfo->mainLocationId;
364 1
                    break;
365 1
                case 'main_language_code':
366 1
                    $value = $location->contentInfo->mainLanguageCode;
367 1
                    break;
368 1
                case 'modification_date':
369 1
                    $value = $location->contentInfo->modificationDate->getTimestamp();
370 1
                    break;
371 1
                case 'name':
372 1
                    $value = $location->contentInfo->name;
373 1
                    break;
374 1
                case 'owner_id':
375 1
                    $value = $location->contentInfo->ownerId;
376 1
                    break;
377 1
                case 'parent_location_id':
378 1
                    $value = $location->parentLocationId;
379 1
                    break;
380 1
                case 'path':
381 1
                    $value = $location->pathString;
382 1
                    break;
383 1
                case 'priority':
384 1
                    $value = $location->priority;
385 1
                    break;
386 1
                case 'publication_date':
387 1
                    $value = $location->contentInfo->publishedDate->getTimestamp();
388 1
                    break;
389 1
                case 'section_id':
390 1
                    $value = $location->contentInfo->sectionId;
391 1
                    break;
392 1
                case 'section_identifier':
393 1
                    $sectionService = $this->repository->getSectionService();
394 1
                    $value = $sectionService->loadSection($location->contentInfo->sectionId)->identifier;
395 1
                    break;
396 1
                case 'sort_field':
397 1
                    $value = $this->sortConverter->sortField2Hash($location->sortField);
398 1
                    break;
399 1
                case 'sort_order':
400 1
                    $value = $this->sortConverter->sortOrder2Hash($location->sortOrder);
401 1
                    break;
402
                default:
403
                    throw new InvalidStepDefinitionException('Location Manager does not support setting references for attribute ' . $reference['attribute']);
404
            }
405
406 2
            $refs[$reference['identifier']] = $value;
407
        }
408
409 3
        return $refs;
410
    }
411
412
    /**
413
     * @param int|string|array $locationKey
414
     * @return Location
415
     */
416 16
    public function matchLocationByKey($locationKey)
417
    {
418 16
        return $this->locationMatcher->matchOneByKey($locationKey);
419
    }
420
421
    /**
422
     * NB: weirdly enough, it returns contents, not locations
423
     *
424
     * @param string $action
425
     * @return ContentCollection
426
     * @throws \Exception
427
     */
428 2
    protected function matchContents($action, $step)
429
    {
430 2
        if (!isset($step->dsl['object_id']) && !isset($step->dsl['remote_id']) && !isset($step->dsl['match'])) {
431
            throw new InvalidStepDefinitionException("The ID or remote ID of an object or a Match Condition is required to $action a new location.");
432
        }
433
434
        // Backwards compat
435 2
        if (!isset($step->dsl['match'])) {
436
            if (isset($step->dsl['object_id'])) {
437
                $step->dsl['match'] = array('content_id' => $step->dsl['object_id']);
438
            } elseif (isset($step->dsl['remote_id'])) {
439
                $step->dsl['match'] = array('content_remote_id' => $step->dsl['remote_id']);
440
            }
441
        }
442
443 2
        $match = $step->dsl['match'];
444
445
        // convert the references passed in the match
446 2
        foreach ($match as $condition => $values) {
447 2
            if (is_array($values)) {
448
                foreach ($values as $position => $value) {
449
                    $match[$condition][$position] = $this->resolveReference($value);
450
                }
451
            } else {
452 2
                $match[$condition] = $this->resolveReference($values);
453
            }
454
        }
455
456 2
        $offset = isset($step->dsl['match_offset']) ? $this->resolveReference($step->dsl['match_offset']) : 0;
457 2
        $limit = isset($step->dsl['match_limit']) ? $this->resolveReference($step->dsl['match_limit']) : 0;
458 2
        $sort = isset($step->dsl['match_sort']) ? $this->resolveReference($step->dsl['match_sort']) : array();
459
460 2
        return $this->contentMatcher->matchContent($match, $sort, $offset, $limit);
461
    }
462
463 1
    protected function setMainLocation(Location $location)
464
    {
465 1
        $contentService = $this->repository->getContentService();
466 1
        $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
467 1
        $contentMetaDataUpdateStruct->mainLocationId = $location->id;
468 1
        $contentService->updateContentMetadata($location->contentInfo, $contentMetaDataUpdateStruct);
469 1
    }
470
471
    /**
472
     * @param $newValue
473
     * @param int $currentValue
474
     * @return int|null
475
     *
476
     * @todo make protected
477
     */
478 1
    public function getSortField($newValue, $currentValue = null)
479
    {
480 1
        $sortField = $currentValue;
481
482 1
        if ($newValue !== null) {
483 1
            $sortField = $this->sortConverter->hash2SortField($newValue);
484
        }
485
486 1
        return $sortField;
487
    }
488
489
    /**
490
     * Get the sort order based on the current value and the value in the DSL definition.
491
     *
492
     * @see \eZ\Publish\API\Repository\Values\Content\Location::SORT_ORDER_*
493
     *
494
     * @param int $newValue
495
     * @param int $currentValue
496
     * @return int|null
497
     *
498
     * @todo make protected
499
     */
500 1
    public function getSortOrder($newValue, $currentValue = null)
501
    {
502 1
        $sortOrder = $currentValue;
503
504 1
        if ($newValue !== null) {
0 ignored issues
show
introduced by
The condition $newValue !== null is always true.
Loading history...
505 1
            $sortOrder = $this->sortConverter->hash2SortOrder($newValue);
506
        }
507
508 1
        return $sortOrder;
509
    }
510
}
511