Completed
Pull Request — master (#127)
by
unknown
16:17 queued 06:53
created

ContentManager::update()   F

Complexity

Conditions 21
Paths 1322

Size

Total Lines 84
Code Lines 46

Duplication

Lines 4
Ratio 4.76 %

Code Coverage

Tests 44
CRAP Score 21.0362

Importance

Changes 0
Metric Value
dl 4
loc 84
ccs 44
cts 46
cp 0.9565
rs 2.0817
c 0
b 0
f 0
cc 21
eloc 46
nc 1322
nop 1
crap 21.0362

How to fix   Long Method    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\ContentType\ContentType;
6
use eZ\Publish\API\Repository\Values\ContentType\FieldDefinition;
7
use eZ\Publish\API\Repository\Values\Content\Content;
8
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct;
9
use eZ\Publish\API\Repository\Values\Content\ContentUpdateStruct;
10
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
11
use Kaliop\eZMigrationBundle\API\Collection\ContentCollection;
12
use Kaliop\eZMigrationBundle\API\MigrationGeneratorInterface;
13
use Kaliop\eZMigrationBundle\Core\FieldHandlerManager;
14
use Kaliop\eZMigrationBundle\Core\Matcher\ContentMatcher;
15
use Kaliop\eZMigrationBundle\Core\Matcher\SectionMatcher;
16
use Kaliop\eZMigrationBundle\Core\Matcher\UserMatcher;
17
use Kaliop\eZMigrationBundle\Core\Matcher\ObjectStateMatcher;
18
use Kaliop\eZMigrationBundle\Core\Matcher\ObjectStateGroupMatcher;
19
use Kaliop\eZMigrationBundle\Core\Helper\SortConverter;
20
use JmesPath\Env as JmesPath;
21
22
/**
23
 * Handles content migrations.
24
 *
25
 * @todo add support for updating of content metadata
26
 */
27
class ContentManager extends RepositoryExecutor implements MigrationGeneratorInterface
28
{
29
    protected $supportedStepTypes = array('content');
30
    protected $supportedActions = array('create', 'load', 'update', 'delete');
31
32
    protected $contentMatcher;
33
    protected $sectionMatcher;
34
    protected $userMatcher;
35
    protected $objectStateMatcher;
36
    protected $objectStateGroupMatcher;
37
    protected $fieldHandlerManager;
38
    protected $locationManager;
39
    protected $sortConverter;
40
41 69
    public function __construct(
42
        ContentMatcher $contentMatcher,
43
        SectionMatcher $sectionMatcher,
44
        UserMatcher $userMatcher,
45
        ObjectStateMatcher $objectStateMatcher,
46
        ObjectStateGroupMatcher $objectStateGroupMatcher,
47
        FieldHandlerManager $fieldHandlerManager,
48
        LocationManager $locationManager,
49
        SortConverter $sortConverter
50
    ) {
51 69
        $this->contentMatcher = $contentMatcher;
52 69
        $this->sectionMatcher = $sectionMatcher;
53 69
        $this->userMatcher = $userMatcher;
54 69
        $this->objectStateMatcher = $objectStateMatcher;
55 69
        $this->objectStateGroupMatcher = $objectStateGroupMatcher;
56 69
        $this->fieldHandlerManager = $fieldHandlerManager;
57 69
        $this->locationManager = $locationManager;
58 69
        $this->sortConverter = $sortConverter;
59 69
    }
60
61
    /**
62
     * Handles the content create migration action type
63
     */
64 9
    protected function create($step)
65
    {
66 9
        $contentService = $this->repository->getContentService();
67 9
        $locationService = $this->repository->getLocationService();
68 9
        $contentTypeService = $this->repository->getContentTypeService();
69
70 9
        $contentTypeIdentifier = $step->dsl['content_type'];
71 9
        $contentTypeIdentifier = $this->referenceResolver->resolveReference($contentTypeIdentifier);
72
        /// @todo use a contenttypematcher
73 9
        $contentType = $contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier);
74
75 9
        $contentCreateStruct = $contentService->newContentCreateStruct($contentType, $this->getLanguageCode($step));
76
77 9
        $this->setFields($contentCreateStruct, $step->dsl['attributes'], $contentType, $step);
78
79 9
        if (isset($step->dsl['always_available'])) {
80
            $contentCreateStruct->alwaysAvailable = $step->dsl['always_available'];
81
        } else {
82
            // Could be removed when https://github.com/ezsystems/ezpublish-kernel/pull/1874 is merged,
83
            // but we strive to support old eZ kernel versions as well...
84 9
            $contentCreateStruct->alwaysAvailable = $contentType->defaultAlwaysAvailable;
85
        }
86
87 9
        if (isset($step->dsl['remote_id'])) {
88 3
            $contentCreateStruct->remoteId = $step->dsl['remote_id'];
89
        }
90
91 9 View Code Duplication
        if (isset($step->dsl['section'])) {
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...
92 1
            $sectionKey = $this->referenceResolver->resolveReference($step->dsl['section']);
93 1
            $section = $this->sectionMatcher->matchOneByKey($sectionKey);
94 1
            $contentCreateStruct->sectionId = $section->id;
95
        }
96
97 9 View Code Duplication
        if (isset($step->dsl['owner'])) {
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...
98 2
            $owner = $this->getUser($step->dsl['owner']);
99 2
            $contentCreateStruct->ownerId = $owner->id;
100
        }
101
102
        // This is a bit tricky, as the eZPublish API does not support having a different creator and owner with only 1 version.
103
        // We allow it, hoping that nothing gets broken because of it
104 9
        if (isset($step->dsl['version_creator'])) {
105 1
            $realContentOwnerId = $contentCreateStruct->ownerId;
106 1
            if ($realContentOwnerId == null) {
107 1
                $realContentOwnerId = $this->repository->getCurrentUser()->id;
108
            }
109 1
            $versionCreator = $this->getUser($step->dsl['version_creator']);
110 1
            $contentCreateStruct->ownerId = $versionCreator->id;
111
        }
112
113 9
        if (isset($step->dsl['modification_date'])) {
114 1
            $contentCreateStruct->modificationDate = $this->toDateTime($step->dsl['modification_date']);
115
        }
116
117
        // instantiate a location create struct from the parent location:
118
        // BC
119 9
        $locationId = isset($step->dsl['parent_location']) ? $step->dsl['parent_location'] : (
120 9
            isset($step->dsl['main_location']) ? $step->dsl['main_location'] : null
121
        );
122
        // 1st resolve references
123 9
        $locationId = $this->referenceResolver->resolveReference($locationId);
124
        // 2nd allow to specify the location via remote_id
125 9
        $locationId = $this->locationManager->matchLocationByKey($locationId)->id;
126 9
        $locationCreateStruct = $locationService->newLocationCreateStruct($locationId);
127
128 9
        if (isset($step->dsl['location_remote_id'])) {
129 2
            $locationCreateStruct->remoteId = $step->dsl['location_remote_id'];
130
        }
131
132 9
        if (isset($step->dsl['priority'])) {
133 1
            $locationCreateStruct->priority = $step->dsl['priority'];
134
        }
135
136 9
        if (isset($step->dsl['is_hidden'])) {
137 1
            $locationCreateStruct->hidden = $step->dsl['is_hidden'];
138
        }
139
140 9
        if (isset($step->dsl['sort_field'])) {
141 1
            $locationCreateStruct->sortField = $this->sortConverter->hash2SortField($step->dsl['sort_field']);
142
        } else {
143 9
            $locationCreateStruct->sortField = $contentType->defaultSortField;
144
        }
145
146 9
        if (isset($step->dsl['sort_order'])) {
147 1
            $locationCreateStruct->sortOrder = $this->sortConverter->hash2SortOrder($step->dsl['sort_order']);
148
        } else {
149 9
            $locationCreateStruct->sortOrder = $contentType->defaultSortOrder;
150
        }
151
152 9
        $locations = array($locationCreateStruct);
153
154
        // BC
155 9
        $other_locations = isset($step->dsl['other_parent_locations']) ? $step->dsl['other_parent_locations'] : (
156 9
            isset($step->dsl['other_locations']) ? $step->dsl['other_locations'] : null
157
        );
158 9
        if (isset($other_locations)) {
159
            foreach ($other_locations as $locationId) {
160
                $locationId = $this->referenceResolver->resolveReference($locationId);
161
                $locationId = $this->locationManager->matchLocationByKey($locationId)->id;
162
                $secondaryLocationCreateStruct = $locationService->newLocationCreateStruct($locationId);
163
                array_push($locations, $secondaryLocationCreateStruct);
164
            }
165
        }
166
167
        // create a draft using the content and location create struct and publish it
168 9
        $draft = $contentService->createContent($contentCreateStruct, $locations);
169 8
        $content = $contentService->publishVersion($draft->versionInfo);
170
171 8
        if (isset($step->dsl['object_states'])) {
172 2
            $this->setObjectStates($content, $step->dsl['object_states']);
173
        }
174
175
        // 2nd part of the hack: re-set the content owner to its intended value
176 8
        if (isset($step->dsl['version_creator']) || isset($step->dsl['publication_date'])) {
177 1
            $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
178
179 1
            if (isset($step->dsl['version_creator'])) {
180 1
                $contentMetaDataUpdateStruct->ownerId = $realContentOwnerId;
0 ignored issues
show
Bug introduced by
The variable $realContentOwnerId does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
181
            }
182 1
            if (isset($step->dsl['publication_date'])) {
183 1
                $contentMetaDataUpdateStruct->publishedDate = $this->toDateTime($step->dsl['publication_date']);
184
            }
185
            // we have to do this to make sure we preserve the custom modification date
186 1
            if (isset($this->dsl['modification_date'])) {
187
                $contentMetaDataUpdateStruct->modificationDate = $this->toDateTime($this->dsl['modification_date']);
0 ignored issues
show
Bug introduced by
The property dsl does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
188
            }
189
190 1
            $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct);
191
        }
192
193 8
        $this->setReferences($content, $step);
194
195 8
        return $content;
196
    }
197
198 3
    protected function load($step)
199
    {
200 3
        $contentCollection = $this->matchContents('load', $step);
201
202
        // This check is already done in setReferences
203
        /*if (count($contentCollection) > 1 && isset($step->dsl['references'])) {
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...
204
            throw new \Exception("Can not execute Content load because multiple contents match, and a references section is specified in the dsl. References can be set when only 1 content matches");
205
        }*/
206
207 3
        $this->setReferences($contentCollection, $step);
0 ignored issues
show
Bug introduced by
It seems like $contentCollection defined by $this->matchContents('load', $step) on line 200 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...
208
209 3
        return $contentCollection;
210
    }
211
212
    /**
213
     * Handles the content update migration action type
214
     *
215
     * @todo handle updating of more metadata fields
216
     */
217 7
    protected function update($step)
218
    {
219 7
        $contentService = $this->repository->getContentService();
220 7
        $contentTypeService = $this->repository->getContentTypeService();
221
222 7
        $contentCollection = $this->matchContents('update', $step);
223
224 7
        if (count($contentCollection) > 1 && isset($step->dsl['references'])) {
225
            throw new \Exception("Can not execute Content update because multiple contents match, and a references section is specified in the dsl. References can be set when only 1 content matches");
226
        }
227
228 7
        $contentType = array();
229
230 7
        foreach ($contentCollection as $key => $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...
231 7
            $contentInfo = $content->contentInfo;
232
233 7
            if (!isset($contentType[$contentInfo->contentTypeId])) {
234 7
                $contentType[$contentInfo->contentTypeId] = $contentTypeService->loadContentType($contentInfo->contentTypeId);
235
            }
236
237 7
            if (isset($step->dsl['attributes']) || isset($step->dsl['version_creator'])) {
238 3
                $contentUpdateStruct = $contentService->newContentUpdateStruct();
239
240 3
                if (isset($step->dsl['attributes'])) {
241 3
                    $this->setFields($contentUpdateStruct, $step->dsl['attributes'], $contentType[$contentInfo->contentTypeId], $step);
242
                }
243
244 3
                $versionCreator = null;
245 3
                if (isset($step->dsl['version_creator'])) {
246 1
                    $versionCreator = $this->getUser($step->dsl['version_creator']);
247
                }
248
249 3
                $draft = $contentService->createContentDraft($contentInfo, null, $versionCreator);
250 3
                $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
251 3
                $content = $contentService->publishVersion($draft->versionInfo);
252
            }
253
254 7
            if (isset($step->dsl['always_available']) ||
255 7
                isset($step->dsl['new_remote_id']) ||
256 6
                isset($step->dsl['owner']) ||
257 5
                isset($step->dsl['modification_date']) ||
258 7
                isset($step->dsl['publication_date'])) {
259
260 2
                $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
261
262 2
                if (isset($step->dsl['always_available'])) {
263
                    $contentMetaDataUpdateStruct->alwaysAvailable = $step->dsl['always_available'];
264
                }
265
266 2
                if (isset($step->dsl['new_remote_id'])) {
267 1
                    $contentMetaDataUpdateStruct->remoteId = $step->dsl['new_remote_id'];
268
                }
269
270 2 View Code Duplication
                if (isset($step->dsl['owner'])) {
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...
271 2
                    $owner = $this->getUser($step->dsl['owner']);
272 2
                    $contentMetaDataUpdateStruct->ownerId = $owner->id;
273
                }
274
275 2
                if (isset($step->dsl['modification_date'])) {
276 1
                    $contentMetaDataUpdateStruct->modificationDate = $this->toDateTime($step->dsl['modification_date']);
277
                }
278
279 2
                if (isset($step->dsl['publication_date'])) {
280 1
                    $contentMetaDataUpdateStruct->publishedDate = $this->toDateTime($step->dsl['publication_date']);
281
                }
282
283 2
                $content = $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct);
284
            }
285
286 7
            if (isset($step->dsl['section'])) {
287 1
                $this->setSection($content, $step->dsl['section']);
288
            }
289
290 7
            if (isset($step->dsl['object_states'])) {
291 1
                $this->setObjectStates($content, $step->dsl['object_states']);
292
            }
293
294 7
            $contentCollection[$key] = $content;
295
        }
296
297 7
        $this->setReferences($contentCollection, $step);
0 ignored issues
show
Bug introduced by
It seems like $contentCollection defined by $this->matchContents('update', $step) on line 222 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...
298
299 7
        return $contentCollection;
300
    }
301
302
    /**
303
     * Handles the content delete migration action type
304
     */
305 7
    protected function delete($step)
306
    {
307 7
        $contentCollection = $this->matchContents('delete', $step);
308
309 7
        $this->setReferences($contentCollection, $step);
0 ignored issues
show
Bug introduced by
It seems like $contentCollection defined by $this->matchContents('delete', $step) on line 307 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...
310
311 7
        $contentService = $this->repository->getContentService();
312
313 7
        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...
314
            try {
315 7
                $contentService->deleteContent($content->contentInfo);
316 7
            } catch (NotFoundException $e) {
317
                // Someone else (or even us, by virtue of location tree?) removed the content which we found just a
318
                // second ago. We can safely ignore this
319
            }
320
        }
321
322 7
        return $contentCollection;
323
    }
324
325
    /**
326
     * @param string $action
327
     * @return ContentCollection
328
     * @throws \Exception
329
     */
330 9 View Code Duplication
    protected function matchContents($action, $step)
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...
331
    {
332 9
        if (!isset($step->dsl['object_id']) && !isset($step->dsl['remote_id']) && !isset($step->dsl['match'])) {
333
            throw new \Exception("The id or remote id of an object or a match condition is required to $action a location");
334
        }
335
336
        // Backwards compat
337
338 9
        if (isset($step->dsl['match'])) {
339 9
            $match = $step->dsl['match'];
340
        } else {
341 1
            if (isset($step->dsl['object_id'])) {
342 1
                $match = array('content_id' => $step->dsl['object_id']);
343
            } elseif (isset($step->dsl['remote_id'])) {
344
                $match = array('content_remote_id' => $step->dsl['remote_id']);
345
            }
346
        }
347
348
        // convert the references passed in the match
349 9
        $match = $this->resolveReferencesRecursively($match);
0 ignored issues
show
Bug introduced by
The variable $match does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Deprecated Code introduced by
The method Kaliop\eZMigrationBundle...ReferencesRecursively() has been deprecated with message: will be moved into the reference resolver classes

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
350
351 9
        return $this->contentMatcher->match($match);
352
    }
353
354
    /**
355
     * Sets references to certain content attributes.
356
     *
357
     * @param \eZ\Publish\API\Repository\Values\Content\Content|ContentCollection $content
358
     * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute
359
     * @return boolean
360
     *
361
     * @todo add support for other attributes... ?
362
     */
363 10
    protected function setReferences($content, $step)
364
    {
365 10
        if (!array_key_exists('references', $step->dsl)) {
366 10
            return false;
367
        }
368
369 6
        $references = $this->setReferencesCommon($content, $step->dsl['references']);
370 6
        $content = $this->insureSingleEntity($content, $references);
371
372 6
        foreach ($references as $reference) {
373
374 6
            switch ($reference['attribute']) {
375 6
                case 'object_id':
376 6
                case 'content_id':
377 6
                case 'id':
378 6
                    $value = $content->id;
379 6
                    break;
380 5
                case 'remote_id':
381 5
                case 'content_remote_id':
382 2
                    $value = $content->contentInfo->remoteId;
383 2
                    break;
384 5
                case 'always_available':
385 1
                    $value = $content->contentInfo->alwaysAvailable;
386 1
                    break;
387 5
                case 'content_type_id':
388 1
                    $value = $content->contentInfo->contentTypeId;
389 1
                    break;
390 5 View Code Duplication
                case 'content_type_identifier':
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...
391 1
                    $contentTypeService = $this->repository->getContentTypeService();
392 1
                    $value = $contentTypeService->loadContentType($content->contentInfo->contentTypeId)->identifier;
393 1
                    break;
394 5
                case 'current_version':
395 5
                case 'current_version_no':
396 1
                    $value = $content->contentInfo->currentVersionNo;
397 1
                    break;
398 5
                case 'location_id':
399 4
                case 'main_location_id':
400 2
                    $value = $content->contentInfo->mainLocationId;
401 2
                    break;
402 4
                case 'main_language_code':
403 1
                    $value = $content->contentInfo->mainLanguageCode;
404 1
                    break;
405 4
                case 'modification_date':
406 1
                    $value = $content->contentInfo->modificationDate->getTimestamp();
407 1
                    break;
408 4
                case 'name':
409 1
                    $value = $content->contentInfo->name;
410 1
                    break;
411 4
                case 'owner_id':
412 1
                    $value = $content->contentInfo->ownerId;
413 1
                    break;
414 4
                case 'path':
415 2
                    $locationService = $this->repository->getLocationService();
416 2
                    $value = $locationService->loadLocation($content->contentInfo->mainLocationId)->pathString;
417 2
                    break;
418 3
                case 'publication_date':
419 1
                    $value = $content->contentInfo->publishedDate->getTimestamp();
420 1
                    break;
421 3
                case 'section_id':
422 1
                    $value = $content->contentInfo->sectionId;
423 1
                    break;
424 3 View Code Duplication
                case 'section_identifier':
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...
425 1
                    $sectionService = $this->repository->getSectionService();
426 1
                    $value = $sectionService->loadSection($content->contentInfo->sectionId)->identifier;
427 1
                    break;
428
                default:
429 2
                    if (strpos($reference['attribute'], 'object_state.') === 0) {
430 1
                        $stateGroupKey = substr($reference['attribute'], 13);
431 1
                        $stateGroup = $this->objectStateGroupMatcher->matchOneByKey($stateGroupKey);
432 1
                        $value = $stateGroupKey . '/' . $this->repository->getObjectStateService()->
433 1
                            getContentState($content->contentInfo, $stateGroup)->identifier;
434 1
                        break;
435
                    }
436
437
                    // allow to get the value of fields as well as their sub-parts
438 1
                    if (strpos($reference['attribute'], 'attributes.') === 0) {
439 1
                        $contentType = $this->repository->getContentTypeService()->loadContentType(
440 1
                            $content->contentInfo->contentTypeId
441
                        );
442 1
                        $parts = explode('.', $reference['attribute']);
443
                        // totally not sure if this list of special chars is correct for what could follow a jmespath identifier...
444
                        // also what about quoted strings?
445 1
                        $fieldIdentifier = preg_replace('/[[(|&!{].*$/', '', $parts[1]);
446 1
                        $field = $content->getField($fieldIdentifier);
447 1
                        $fieldDefinition = $contentType->getFieldDefinition($fieldIdentifier);
448 1
                        $hashValue = $this->fieldHandlerManager->fieldValueToHash(
449 1
                            $fieldDefinition->fieldTypeIdentifier, $contentType->identifier, $field->value
450
                        );
451 1
                        if (is_array($hashValue) ) {
452 1 View Code Duplication
                            if (count($parts) == 2 && $fieldIdentifier === $parts[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...
453
                                throw new \InvalidArgumentException('Content Manager does not support setting references for attribute ' . $reference['attribute'] . ': the given attribute has an array value');
454
                            }
455 1
                            $value = JmesPath::search(implode('.', array_slice($parts, 1)), array($fieldIdentifier => $hashValue));
456
                        } else {
457
                            if (count($parts) > 2) {
458
                                throw new \InvalidArgumentException('Content Manager does not support setting references for attribute ' . $reference['attribute'] . ': the given attribute has a scalar value');
459
                            }
460
                            $value = $hashValue;
461
                        }
462 1
                        break;
463
                    }
464
465
                    throw new \InvalidArgumentException('Content Manager does not support setting references for attribute ' . $reference['attribute']);
466
            }
467
468 6
            $overwrite = false;
469 6
            if (isset($reference['overwrite'])) {
470
                $overwrite = $reference['overwrite'];
471
            }
472 6
            $this->referenceResolver->addReference($reference['identifier'], $value, $overwrite);
473
        }
474
475 6
        return true;
476
    }
477
478
    /**
479
     * @param array $matchCondition
480
     * @param string $mode
481
     * @param array $context
482
     * @throws \Exception
483
     * @return array
484
     *
485
     * @todo add support for dumping all object languages
486
     * @todo add 2ndary locations when in 'update' mode
487
     * @todo add dumping of sort_field and sort_order for 2ndary locations
488
     */
489 3
    public function generateMigration(array $matchCondition, $mode, array $context = array())
490
    {
491 3
        $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($context));
492 3
        $contentCollection = $this->contentMatcher->match($matchCondition);
493 3
        $data = array();
494
495
        /** @var \eZ\Publish\API\Repository\Values\Content\Content $content */
496 3
        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...
497
498 3
            $location = $this->repository->getLocationService()->loadLocation($content->contentInfo->mainLocationId);
499 3
            $contentType = $this->repository->getContentTypeService()->loadContentType(
500 3
                $content->contentInfo->contentTypeId
501
            );
502
503
            $contentData = array(
504 3
                'type' => reset($this->supportedStepTypes),
505 3
                'mode' => $mode
506
            );
507
508
            switch ($mode) {
509 3
                case 'create':
510 1
                    $contentData = array_merge(
511 1
                        $contentData,
512
                        array(
513 1
                            'content_type' => $contentType->identifier,
514 1
                            'parent_location' => $location->parentLocationId,
515 1
                            'priority' => $location->priority,
516 1
                            'is_hidden' => $location->invisible,
517 1
                            'sort_field' => $this->sortConverter->sortField2Hash($location->sortField),
518 1
                            'sort_order' => $this->sortConverter->sortOrder2Hash($location->sortOrder),
519 1
                            'remote_id' => $content->contentInfo->remoteId,
520 1
                            'location_remote_id' => $location->remoteId
521
                        )
522
                    );
523 1
                    $locationService = $this->repository->getLocationService();
524 1
                    $locations = $locationService->loadLocations($content->contentInfo);
525 1
                    if (count($locations) > 1) {
526
                        $otherParentLocations = array();
527
                        foreach($locations as $otherLocation) {
528
                            if ($otherLocation->id != $location->id) {
529
                                $otherParentLocations[] = $otherLocation->parentLocationId;
530
                            }
531
                        }
532
                        $contentData['other_parent_locations'] = $otherParentLocations;
533
                    }
534 1
                    break;
535 2 View Code Duplication
                case 'update':
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...
536 1
                    $contentData = array_merge(
537 1
                        $contentData,
538
                        array(
539
                            'match' => array(
540 1
                                ContentMatcher::MATCH_CONTENT_REMOTE_ID => $content->contentInfo->remoteId
541
                            ),
542 1
                            'new_remote_id' => $content->contentInfo->remoteId,
543
                        )
544
                    );
545 1
                    break;
546 1 View Code Duplication
                case 'delete':
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...
547 1
                    $contentData = array_merge(
548 1
                        $contentData,
549
                        array(
550
                            'match' => array(
551 1
                                ContentMatcher::MATCH_CONTENT_REMOTE_ID => $content->contentInfo->remoteId
552
                            )
553
                        )
554
                    );
555 1
                    break;
556
                default:
557
                    throw new \Exception("Executor 'content' doesn't support mode '$mode'");
558
            }
559
560 3
            if ($mode != 'delete') {
561
562 2
                $attributes = array();
563 2
                foreach ($content->getFieldsByLanguage($this->getLanguageCodeFromContext($context)) as $fieldIdentifier => $field) {
564 2
                    $fieldDefinition = $contentType->getFieldDefinition($fieldIdentifier);
565 2
                    $attributes[$field->fieldDefIdentifier] = $this->fieldHandlerManager->fieldValueToHash(
566 2
                        $fieldDefinition->fieldTypeIdentifier, $contentType->identifier, $field->value
567
                    );
568
                }
569
570 2
                $contentData = array_merge(
571 2
                    $contentData,
572
                    array(
573 2
                        'lang' => $this->getLanguageCodeFromContext($context),
574 2
                        'section' => $content->contentInfo->sectionId,
575 2
                        'owner' => $content->contentInfo->ownerId,
576 2
                        'modification_date' => $content->contentInfo->modificationDate->getTimestamp(),
577 2
                        'publication_date' => $content->contentInfo->publishedDate->getTimestamp(),
578 2
                        'always_available' => (bool)$content->contentInfo->alwaysAvailable,
579 2
                        'attributes' => $attributes
580
                    )
581
                );
582
            }
583
584 3
            $data[] = $contentData;
585
        }
586
587 3
        $this->loginUser($previousUserId);
588 3
        return $data;
589
    }
590
591
    /**
592
     * Helper function to set the fields of a ContentCreateStruct based on the DSL attribute settings.
593
     *
594
     * @param ContentCreateStruct|ContentUpdateStruct $createOrUpdateStruct
595
     * @param array $fields see description of expected format in code below
596
     * @param ContentType $contentType
597
     * @param $step
598
     * @throws \Exception
599
     */
600 9
    protected function setFields($createOrUpdateStruct, array $fields, ContentType $contentType, $step)
601
    {
602 9
        $i = 0;
603
        // the 'easy' yml: key = field name, value = value
604
        // deprecated: the 'legacy' yml: key = numerical index, value = array ( field name => value )
605 9
        foreach ($fields as $key => $field) {
606
607 9
            if ($key === $i && is_array($field) && count($field) == 1) {
608
                // each $field is one key value pair
609
                // eg.: $field = array($fieldIdentifier => $fieldValue)
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% 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...
610 7
                reset($field);
611 7
                $fieldIdentifier = key($field);
612 7
                $fieldValue = $field[$fieldIdentifier];
613
            } else {
614 3
                $fieldIdentifier = $key;
615 3
                $fieldValue = $field;
616
            }
617
618 9
            if (!isset($contentType->fieldDefinitionsByIdentifier[$fieldIdentifier])) {
0 ignored issues
show
Bug introduced by
The property fieldDefinitionsByIdentifier does not seem to exist. Did you mean fieldDefinitions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
619
                throw new \Exception("Field '$fieldIdentifier' is not present in content type '{$contentType->identifier}'");
620
            }
621
622 9
            $fieldDefinition = $contentType->fieldDefinitionsByIdentifier[$fieldIdentifier];
0 ignored issues
show
Bug introduced by
The property fieldDefinitionsByIdentifier does not seem to exist. Did you mean fieldDefinitions?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
623 9
            $fieldValue = $this->getFieldValue($fieldValue, $fieldDefinition, $contentType->identifier, $step->context);
624
625 9
            $createOrUpdateStruct->setField($fieldIdentifier, $fieldValue, $this->getLanguageCode($step));
626
627 9
            $i++;
628
        }
629 9
    }
630
631 1 View Code Duplication
    protected function setSection(Content $content, $sectionKey)
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...
632
    {
633 1
        $sectionKey = $this->referenceResolver->resolveReference($sectionKey);
634 1
        $section = $this->sectionMatcher->matchOneByKey($sectionKey);
635
636 1
        $sectionService = $this->repository->getSectionService();
637 1
        $sectionService->assignSection($content->contentInfo, $section);
638 1
    }
639
640 2
    protected function setObjectStates(Content $content, array $stateKeys)
641
    {
642 2
        foreach ($stateKeys as $stateKey) {
643 2
            $stateKey = $this->referenceResolver->resolveReference($stateKey);
644
            /** @var \eZ\Publish\API\Repository\Values\ObjectState\ObjectState $state */
645 2
            $state = $this->objectStateMatcher->matchOneByKey($stateKey);
646
647 2
            $stateService = $this->repository->getObjectStateService();
648 2
            $stateService->setContentState($content->contentInfo, $state->getObjectStateGroup(), $state);
649
        }
650 2
    }
651
652
    /**
653
     * Create the field value from the migration definition hash
654
     *
655
     * @param mixed $value
656
     * @param FieldDefinition $fieldDefinition
657
     * @param string $contentTypeIdentifier
658
     * @param array $context
659
     * @throws \InvalidArgumentException
660
     * @return mixed
661
     */
662 9
    protected function getFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array())
663
    {
664 9
        $fieldTypeIdentifier = $fieldDefinition->fieldTypeIdentifier;
665 9
        if (is_array($value) || $this->fieldHandlerManager->managesField($fieldTypeIdentifier, $contentTypeIdentifier)) {
666
            // inject info about the current content type and field into the context
667 3
            $context['contentTypeIdentifier'] = $contentTypeIdentifier;
668 3
            $context['fieldIdentifier'] = $fieldDefinition->identifier;
669 3
            return $this->fieldHandlerManager->hashToFieldValue($fieldTypeIdentifier, $contentTypeIdentifier, $value, $context);
670
        }
671
672 9
        return $this->getSingleFieldValue($value, $fieldDefinition, $contentTypeIdentifier, $context);
673
    }
674
675
    /**
676
     * Create the field value for a primitive field from the migration definition hash
677
     *
678
     * @param mixed $value
679
     * @param FieldDefinition $fieldDefinition
680
     * @param string $contentTypeIdentifier
681
     * @param array $context
682
     * @throws \InvalidArgumentException
683
     * @return mixed
684
     */
685 9
    protected function getSingleFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array())
0 ignored issues
show
Unused Code introduced by
The parameter $fieldDefinition is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $contentTypeIdentifier is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
686
    {
687
        // booleans were handled here. They are now handled as complextypes
688
689
        // q: do we really want this to happen by default on all scalar field values?
690
        // Note: if you want this *not* to happen, register a complex field for your scalar field...
691 9
        $value = $this->referenceResolver->resolveReference($value);
692
693 9
        return $value;
694
    }
695
696
    /**
697
     * Load user using either login, email, id - resolving eventual references
698
     * @param int|string $userKey
699
     * @return \eZ\Publish\API\Repository\Values\User\User
700
     */
701 2
    protected function getUser($userKey)
702
    {
703 2
        $userKey = $this->referenceResolver->resolveReference($userKey);
704 2
        return $this->userMatcher->matchOneByKey($userKey);
705
    }
706
707
    /**
708
     * @param int|string $date if integer, we assume a timestamp
709
     * @return \DateTime
710
     */
711 1
    protected function toDateTime($date)
712
    {
713 1
        if (is_int($date)) {
714
            return new \DateTime("@" . $date);
715
        } else {
716 1
            return new \DateTime($date);
717
        }
718
    }
719
}
720