Completed
Push — master ( a5233f...937e4c )
by Gaetano
06:11
created

ContentManager::create()   F

Complexity

Conditions 24
Paths > 20000

Size

Total Lines 129
Code Lines 71

Duplication

Lines 9
Ratio 6.98 %

Code Coverage

Tests 43
CRAP Score 32.5331

Importance

Changes 0
Metric Value
dl 9
loc 129
ccs 43
cts 57
cp 0.7544
rs 2
c 0
b 0
f 0
cc 24
eloc 71
nc 983040
nop 0
crap 32.5331

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 Kaliop\eZMigrationBundle\API\Collection\ContentCollection;
11
use Kaliop\eZMigrationBundle\API\MigrationGeneratorInterface;
12
use Kaliop\eZMigrationBundle\Core\ComplexField\ComplexFieldManager;
13
use Kaliop\eZMigrationBundle\Core\Matcher\ContentMatcher;
14
use Kaliop\eZMigrationBundle\Core\Matcher\SectionMatcher;
15
use Kaliop\eZMigrationBundle\Core\Matcher\UserMatcher;
16
use Kaliop\eZMigrationBundle\Core\Matcher\ObjectStateMatcher;
17
use Kaliop\eZMigrationBundle\Core\Helper\SortConverter;
18
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
19
20
/**
21
 * Implements the actions for managing (create/update/delete) Content in the system through
22
 * migrations and abstracts away the eZ Publish Public API.
23
 *
24
 * @todo add support for updating of content metadata
25
 */
26
class ContentManager extends RepositoryExecutor implements MigrationGeneratorInterface
27 20
{
28
    protected $supportedStepTypes = array('content');
29 20
    protected $supportedActions = array('create', 'load', 'update', 'delete');
30 20
31 20
    protected $contentMatcher;
32
    protected $sectionMatcher;
33
    protected $userMatcher;
34
    protected $objectStateMatcher;
35
    protected $complexFieldManager;
36 4
    protected $locationManager;
37
    protected $sortConverter;
38 4
39 4
    public function __construct(
40 4
        ContentMatcher $contentMatcher,
41
        SectionMatcher $sectionMatcher,
42 4
        UserMatcher $userMatcher,
43 4
        ObjectStateMatcher $objectStateMatcher,
44 3
        ComplexFieldManager $complexFieldManager,
45 3
        LocationManager $locationManager,
46 4
        SortConverter $sortConverter
47
    ) {
48 4
        $this->contentMatcher = $contentMatcher;
49 4
        $this->sectionMatcher = $sectionMatcher;
50
        $this->userMatcher = $userMatcher;
51 4
        $this->objectStateMatcher = $objectStateMatcher;
52 1
        $this->complexFieldManager = $complexFieldManager;
53 1
        $this->locationManager = $locationManager;
54
        $this->sortConverter = $sortConverter;
55
    }
56 4
57 4
    /**
58 1
     * Handle the content create migration action type
59 1
     */
60 4
    protected function create()
61 4
    {
62 1
        $contentService = $this->repository->getContentService();
63 1
        $locationService = $this->repository->getLocationService();
64
        $contentTypeService = $this->repository->getContentTypeService();
65 4
66 1
        $contentTypeIdentifier = $this->dsl['content_type'];
67 1
        $contentTypeIdentifier = $this->referenceResolver->resolveReference($contentTypeIdentifier);
68
        /// @todo use a contenttypematcher
69 4
        $contentType = $contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier);
70
71 4
        $contentCreateStruct = $contentService->newContentCreateStruct($contentType, $this->getLanguageCode());
72
73
        $this->setFields($contentCreateStruct, $this->dsl['attributes'], $contentType);
74
75
        if (isset($this->dsl['always_available'])) {
76
            $contentCreateStruct->alwaysAvailable = $this->dsl['always_available'];
77
        } else {
78
            // Could be removed when https://github.com/ezsystems/ezpublish-kernel/pull/1874 is merged,
79
            // but we strive to support old eZ kernel versions as well...
80
            $contentCreateStruct->alwaysAvailable = $contentType->defaultAlwaysAvailable;
81
        }
82
83 4
        if (isset($this->dsl['remote_id'])) {
84 3
            $contentCreateStruct->remoteId = $this->dsl['remote_id'];
85
        }
86 3
87 3 View Code Duplication
        if (isset($this->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...
88
            $sectionKey = $this->referenceResolver->resolveReference($this->dsl['section']);
89
            $section = $this->sectionMatcher->matchOneByKey($sectionKey);
90
            $contentCreateStruct->sectionId = $section->id;
91
        }
92
93 View Code Duplication
        if (isset($this->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...
94 1
            $owner = $this->getUser($this->dsl['owner']);
95
            $contentCreateStruct->ownerId = $owner->id;
96 1
        }
97 1
98
        // This is a bit tricky, as the eZPublish API does not support having a different creator and owner with only 1 version.
99
        // We allow it, hoping that nothing gets broken because of it
100
        if (isset($this->dsl['version_creator'])) {
101
            $realContentOwnerId = $contentCreateStruct->ownerId;
102
            if ($realContentOwnerId == null) {
103
                $realContentOwnerId = $this->repository->getCurrentUser()->id;
104
            }
105
            $versionCreator = $this->getUser($this->dsl['version_creator']);
106
            $contentCreateStruct->ownerId = $versionCreator->id;
107
        }
108
109
        if (isset($this->dsl['modification_date'])) {
110
            $contentCreateStruct->modificationDate = $this->toDateTime($this->dsl['modification_date']);
111
        }
112
113
        // instantiate a location create struct from the parent location:
114
        // BC
115
        $locationId = isset($this->dsl['parent_location']) ? $this->dsl['parent_location'] : (
116
            isset($this->dsl['main_location']) ? $this->dsl['main_location'] : null
117
        );
118
        // 1st resolve references
119
        $locationId = $this->referenceResolver->resolveReference($locationId);
120
        // 2nd allow to specify the location via remote_id
121 1
        $locationId = $this->locationManager->matchLocationByKey($locationId)->id;
122
        $locationCreateStruct = $locationService->newLocationCreateStruct($locationId);
123 1
124
        if (isset($this->dsl['location_remote_id'])) {
125
            $locationCreateStruct->remoteId = $this->dsl['location_remote_id'];
126
        }
127 1
128
        if (isset($this->dsl['priority'])) {
129 1
            $locationCreateStruct->priority = $this->dsl['priority'];
130 1
        }
131
132 1
        if (isset($this->dsl['is_hidden'])) {
133 1
            $locationCreateStruct->hidden = $this->dsl['is_hidden'];
134 1
        }
135
136 1
        if (isset($this->dsl['sort_field'])) {
137
            $locationCreateStruct->sortField = $this->sortConverter->hash2SortField($this->dsl['sort_field']);
138 1
        } else {
139 1
            $locationCreateStruct->sortField = $contentType->defaultSortField;
140 1
        }
141
142 1
        if (isset($this->dsl['sort_order'])) {
143 1
            $locationCreateStruct->sortOrder = $this->sortConverter->hash2SortOrder($this->dsl['sort_order']);
144 1
        } else {
145
            $locationCreateStruct->sortOrder = $contentType->defaultSortOrder;
146 1
        }
147
148
        $locations = array($locationCreateStruct);
149
150
        // BC
151
        $other_locations = isset($this->dsl['other_parent_locations']) ? $this->dsl['other_parent_locations'] : (
152
            isset($this->dsl['other_locations']) ? $this->dsl['other_locations'] : null
153
        );
154
        if (isset($other_locations)) {
155
            foreach ($other_locations as $locationId) {
156
                $locationId = $this->referenceResolver->resolveReference($locationId);
157
                $locationId = $this->locationManager->matchLocationByKey($locationId)->id;
158
                $secondaryLocationCreateStruct = $locationService->newLocationCreateStruct($locationId);
159
                array_push($locations, $secondaryLocationCreateStruct);
160
            }
161 1
        }
162 1
163 1
        // create a draft using the content and location create struct and publish it
164
        $draft = $contentService->createContent($contentCreateStruct, $locations);
165
        $content = $contentService->publishVersion($draft->versionInfo);
166
167
        if (isset($this->dsl['object_states'])) {
168 3
            $this->setObjectStates($content, $this->dsl['object_states']);
169
        }
170 3
171
        // 2nd part of the hack: re-set the content owner to its intended value
172 3
        if (isset($this->dsl['version_creator']) || isset($this->dsl['publication_date'])) {
173
            $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
174 3
175
            if (isset($this->dsl['version_creator'])) {
176 3
                $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...
177 3
            }
178
            if (isset($this->dsl['publication_date'])) {
179
                $contentMetaDataUpdateStruct->publishedDate = $this->toDateTime($this->dsl['publication_date']);
180
            }
181 3
182 3
            $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct);
183
        }
184
185
        $this->setReferences($content);
186
187
        return $content;
188
    }
189 3
190 View Code Duplication
    protected function load()
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...
191 3
    {
192
        $contentCollection = $this->matchContents('load');
193
194
        if (count($contentCollection) > 1 && isset($this->dsl['references'])) {
195
            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");
196 3
        }
197 1
198 1
        $this->setReferences($contentCollection);
0 ignored issues
show
Bug introduced by
It seems like $contentCollection defined by $this->matchContents('load') on line 192 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...
199 1
200
        return $contentCollection;
201
    }
202 1
203
    /**
204 3
     * Handle the content update migration action type
205
     *
206
     * @todo handle updating of more metadata fields
207 3
     */
208 3
    protected function update()
209 1
    {
210 1
        $contentService = $this->repository->getContentService();
211 1
        $contentTypeService = $this->repository->getContentTypeService();
212 1
213 1
        $contentCollection = $this->matchContents('update');
214 1
215 3
        if (count($contentCollection) > 1 && isset($this->dsl['references'])) {
216 3
            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");
217 3
        }
218
219 3
        $contentType = null;
220
221 3
        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...
222
            $contentInfo = $content->contentInfo;
223
224
            if ($contentType == null) {
225
                $contentType = $contentTypeService->loadContentType($contentInfo->contentTypeId);
226
            }
227
228
            $contentUpdateStruct = $contentService->newContentUpdateStruct();
229
230
            if (isset($this->dsl['attributes'])) {
231 4
                $this->setFields($contentUpdateStruct, $this->dsl['attributes'], $contentType);
232
            }
233 4
234
            $versionCreator = null;
235
            if (isset($this->dsl['version_creator'])) {
236 4
                $versionCreator = $this->getUser($this->dsl['version_creator']);
237
            }
238 4
239
            $draft = $contentService->createContentDraft($contentInfo, null, $versionCreator);
240 4
            $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
241
            $content = $contentService->publishVersion($draft->versionInfo);
242 1
243 1
            if (isset($this->dsl['always_available']) ||
244
                isset($this->dsl['new_remote_id']) ||
245 4
                isset($this->dsl['owner']) ||
246 3
                isset($this->dsl['modification_date']) ||
247
                isset($this->dsl['publication_date'])) {
248 4
249 4
                $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
250 4
251
                if (isset($this->dsl['always_available'])) {
252
                    $contentMetaDataUpdateStruct->alwaysAvailable = $this->dsl['always_available'];
253
                }
254
255
                if (isset($this->dsl['new_remote_id'])) {
256
                    $contentMetaDataUpdateStruct->remoteId = $this->dsl['new_remote_id'];
257
                }
258
259 View Code Duplication
                if (isset($this->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...
260
                    $owner = $this->getUser($this->dsl['owner']);
261
                    $contentMetaDataUpdateStruct->ownerId = $owner->id;
262 4
                }
263
264 4
                if (isset($this->dsl['modification_date'])) {
265 4
                    $contentMetaDataUpdateStruct->modificationDate = $this->toDateTime($this->dsl['modification_date']);
266
                }
267
268 4
                if (isset($this->dsl['publication_date'])) {
269 4
                    $contentMetaDataUpdateStruct->publishedDate = $this->toDateTime($this->dsl['publication_date']);
270 4
                }
271
272 4
                $content = $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct);
273 1
            }
274 1
275
            if (isset($this->dsl['section'])) {
276 4
                $this->setSection($content, $this->dsl['section']);
277
            }
278
279
            if (isset($this->dsl['object_states'])) {
280
                $this->setObjectStates($content, $this->dsl['object_states']);
281
            }
282
283
            $contentCollection[$key] = $content;
284
        }
285
286
        $this->setReferences($contentCollection);
0 ignored issues
show
Bug introduced by
It seems like $contentCollection defined by $this->matchContents('update') on line 213 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...
287 1
288
        return $contentCollection;
289 1
    }
290 1
291
    /**
292
     * Handle the content delete migration action type
293
     */
294
    protected function delete()
295
    {
296
        $contentService = $this->repository->getContentService();
297
298
        $contentCollection = $this->matchContents('delete');
299
300
        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...
301
            try {
302 3
                $contentService->deleteContent($content->contentInfo);
303
            } catch (NotFoundException $e) {
304 3
                // Someone else (or even us, by virtue of location tree?) removed the content which we found just a
305 2
                // second ago. We can safely ignore this
306
            }
307
        }
308 3
309
        return $contentCollection;
310
    }
311
312
    /**
313
     * @param string $action
314
     * @return ContentCollection
315 3
     * @throws \Exception
316
     */
317 3 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...
318 3
    {
319 3
        if (!isset($this->dsl['object_id']) && !isset($this->dsl['remote_id']) && !isset($this->dsl['match'])) {
320 3
            throw new \Exception("The ID or remote ID of an object or a Match Condition is required to $action a new location.");
321 3
        }
322 3
323 2
        // Backwards compat
324 2
        if (!isset($this->dsl['match'])) {
325 2
            if (isset($this->dsl['object_id'])) {
326 1
                $this->dsl['match'] = array('content_id' => $this->dsl['object_id']);
327 1
            } elseif (isset($this->dsl['remote_id'])) {
328 1
                $this->dsl['match'] = array('content_remote_id' => $this->dsl['remote_id']);
329 1
            }
330
        }
331
332
        $match = $this->dsl['match'];
333 3
334
        // convert the references passed in the match
335 3
        foreach ($match as $condition => $values) {
336 3
            if (is_array($values)) {
337
                foreach ($values as $position => $value) {
338 3
                    $match[$condition][$position] = $this->referenceResolver->resolveReference($value);
339
                }
340
            } else {
341
                $match[$condition] = $this->referenceResolver->resolveReference($values);
342
            }
343
        }
344
345
        return $this->contentMatcher->match($match);
346
    }
347
348
    /**
349
     * Helper function to set the fields of a ContentCreateStruct based on the DSL attribute settings.
350
     *
351
     * @param ContentCreateStruct|ContentUpdateStruct $createOrUpdateStruct
352
     * @param ContentType $contentType
353
     * @param array $fields see description of expected format in code below
354
     * @throws \Exception
355
     */
356
    protected function setFields($createOrUpdateStruct, array $fields, ContentType $contentType)
357
    {
358
        $i = 0;
359
        // the 'easy' yml: key = field name, value = value
360
        // deprecated: the 'legacy' yml: key = numerical index, value = array ( field name => value )
361
        foreach ($fields as $key => $field) {
362
363
            if ($key === $i && is_array($field) && count($field) == 1) {
364
                // each $field is one key value pair
365
                // 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...
366
                reset($field);
367
                $fieldIdentifier = key($field);
368
                $fieldValue = $field[$fieldIdentifier];
369
            } else {
370
                $fieldIdentifier = $key;
371
                $fieldValue = $field;
372
            }
373
374
            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...
375
                throw new \Exception("Field '$fieldIdentifier' is not present in field type '{$contentType->identifier}'");
376
            }
377
378
            $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...
379
            $fieldValue = $this->getFieldValue($fieldValue, $fieldDefinition, $contentType->identifier, $this->context);
380
381
            $createOrUpdateStruct->setField($fieldIdentifier, $fieldValue, $this->getLanguageCode());
382
383
            $i++;
384
        }
385
    }
386
387 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...
388
    {
389
        $sectionKey = $this->referenceResolver->resolveReference($sectionKey);
390
        $section = $this->sectionMatcher->matchOneByKey($sectionKey);
391
392
        $sectionService = $this->repository->getSectionService();
393
        $sectionService->assignSection($content->contentInfo, $section);
394
    }
395
396
    protected function setObjectStates(Content $content, array $stateKeys)
397
    {
398
        foreach ($stateKeys as $stateKey) {
399
            $stateKey = $this->referenceResolver->resolveReference($stateKey);
400
            /** @var \eZ\Publish\API\Repository\Values\ObjectState\ObjectState $state */
401
            $state = $this->objectStateMatcher->matchOneByKey($stateKey);
402
403
            $stateService = $this->repository->getObjectStateService();
404
            $stateService->setContentState($content->contentInfo, $state->getObjectStateGroup(), $state);
405
        }
406
    }
407
408
    /**
409
     * Create the field value from the migration definition hash
410
     *
411
     * @param mixed $value
412
     * @param FieldDefinition $fieldDefinition
413
     * @param string $contentTypeIdentifier
414
     * @param array $context
415
     * @throws \InvalidArgumentException
416
     * @return mixed
417
     */
418
    protected function getFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array())
419
    {
420
        $fieldTypeIdentifier = $fieldDefinition->fieldTypeIdentifier;
421
        if (is_array($value) || $this->complexFieldManager->managesField($fieldTypeIdentifier, $contentTypeIdentifier)) {
422
            // inject info about the current content type and field into the context
423
            $context['contentTypeIdentifier'] = $contentTypeIdentifier;
424
            $context['fieldIdentifier'] = $fieldDefinition->identifier;
425
            return $this->complexFieldManager->hashToFieldValue($fieldTypeIdentifier, $contentTypeIdentifier, $value, $context);
426
        }
427
428
        return $this->getSingleFieldValue($value, $fieldDefinition, $contentTypeIdentifier, $context);
429
    }
430
431
    /**
432
     * Create the field value for a primitive field from the migration definition hash
433
     *
434
     * @param mixed $value
435
     * @param FieldDefinition $fieldDefinition
436
     * @param string $contentTypeIdentifier
437
     * @param array $context
438
     * @throws \InvalidArgumentException
439
     * @return mixed
440
     */
441
    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...
442
    {
443
        // booleans were handled here. They are now handled as complextypes
444
445
        // q: do we really want this to happen by default on all scalar field values?
446
        // Note: if you want this *not* to happen, register a complex field for your scalar field...
447
        $value = $this->referenceResolver->resolveReference($value);
448
449
        return $value;
450
    }
451
452
    /**
453
     * Load user using either login, email, id - resolving eventual references
454
     * @param int|string $userKey
455
     * @return \eZ\Publish\API\Repository\Values\User\User
456
     */
457
    protected function getUser($userKey)
458
    {
459
        $userKey = $this->referenceResolver->resolveReference($userKey);
460
        return $this->userMatcher->matchOneByKey($userKey);
461
    }
462
463
    /**
464
     * Sets references to certain content attributes.
465
     *
466
     * @param \eZ\Publish\API\Repository\Values\Content\Content|ContentCollection $content
467
     * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute
468
     * @return boolean
469
     *
470
     * @todo add support for other attributes: contentTypeId, contentTypeIdentifier, section, etc... ?
471
     */
472
    protected function setReferences($content)
473
    {
474
        if (!array_key_exists('references', $this->dsl)) {
475
            return false;
476
        }
477
478
        if ($content instanceof ContentCollection) {
479
            if (count($content) > 1) {
480
                throw new \InvalidArgumentException('Content Manager does not support setting references for creating/updating of multiple contents');
481
            }
482
            $content = reset($content);
483
        }
484
485
        foreach ($this->dsl['references'] as $reference) {
486
487
            switch ($reference['attribute']) {
488
                case 'object_id':
489
                case 'content_id':
490
                case 'id':
491
                    $value = $content->id;
492
                    break;
493
                case 'remote_id':
494
                case 'content_remote_id':
495
                    $value = $content->contentInfo->remoteId;
496
                    break;
497
                case 'always_available':
498
                    $value = $content->contentInfo->alwaysAvailable;
499
                    break;
500
                case 'content_type_id':
501
                    $value = $content->contentInfo->contentTypeId;
502
                    break;
503 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...
504
                    $contentTypeService = $this->repository->getContentTypeService();
505
                    $value = $contentTypeService->loadContentType($content->contentInfo->contentTypeId)->identifier;
506
                    break;
507
                case 'current_version':
508
                case 'current_version_no':
509
                    $value = $content->contentInfo->currentVersionNo;
510
                    break;
511
                case 'location_id':
512
                case 'main_location_id':
513
                    $value = $content->contentInfo->mainLocationId;
514
                    break;
515
                case 'main_language_code':
516
                    $value = $content->contentInfo->mainLanguageCode;
517
                    break;
518
                case 'modification_date':
519
                    $value = $content->contentInfo->modificationDate->getTimestamp();
520
                    break;
521
                case 'name':
522
                    $value = $content->contentInfo->name;
523
                    break;
524
                case 'owner_id':
525
                    $value = $content->contentInfo->ownerId;
526
                    break;
527
                case 'path':
528
                    $locationService = $this->repository->getLocationService();
529
                    $value = $locationService->loadLocation($content->contentInfo->mainLocationId)->pathString;
530
                    break;
531
                case 'publication_date':
532
                    $value = $content->contentInfo->publishedDate->getTimestamp();
533
                    break;
534
                case 'section_id':
535
                    $value = $content->contentInfo->sectionId;
536
                    break;
537
                default:
538
                    throw new \InvalidArgumentException('Content Manager does not support setting references for attribute ' . $reference['attribute']);
539
            }
540
541
            $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...ver\ChainPrefixResolver, Kaliop\eZMigrationBundle...ver\ChainRegexpResolver, Kaliop\eZMigrationBundle...eResolver\ChainResolver, 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...
542
        }
543
544
        return true;
545
    }
546
547
    /**
548
     * @param int|string $date if integer, we assume a timestamp
549
     * @return \DateTime
550
     */
551
    protected function toDateTime($date)
552
    {
553
        if (is_int($date)) {
554
            return new \DateTime("@" . $date);
555
        } else {
556
            return new \DateTime($date);
557
        }
558
    }
559
560
    /**
561
     * @param array $matchCondition
562
     * @param string $mode
563
     * @throws \Exception
564
     * @return array
565
     *
566
     * @todo add support for dumping all object languages
567
     * @todo add 2ndary locations when in 'update' mode
568
     * @todo add dumping of sort_field and sort_order for 2ndary locations
569
     */
570
    public function generateMigration(array $matchCondition, $mode)
571
    {
572
        $previousUserId = $this->loginUser(self::ADMIN_USER_ID);
573
        $contentCollection = $this->contentMatcher->match($matchCondition);
574
        $data = array();
575
576
        /** @var \eZ\Publish\API\Repository\Values\Content\Content $content */
577
        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...
578
579
            $location = $this->repository->getLocationService()->loadLocation($content->contentInfo->mainLocationId);
580
            $contentType = $this->repository->getContentTypeService()->loadContentType(
581
                $content->contentInfo->contentTypeId
582
            );
583
584
            $contentData = array(
585
                'type' => reset($this->supportedStepTypes),
586
                'mode' => $mode
587
            );
588
589
            switch ($mode) {
590
                case 'create':
591
                    $contentData = array_merge(
592
                        $contentData,
593
                        array(
594
                            'content_type' => $contentType->identifier,
595
                            'parent_location' => $location->parentLocationId,
596
                            'priority' => $location->priority,
597
                            'is_hidden' => $location->invisible,
598
                            'sort_field' => $this->sortConverter->sortField2Hash($location->sortField),
599
                            'sort_order' => $this->sortConverter->sortOrder2Hash($location->sortOrder),
600
                            'remote_id' => $content->contentInfo->remoteId,
601
                            'location_remote_id' => $location->remoteId
602
                        )
603
                    );
604
                    $locationService = $this->repository->getLocationService();
605
                    $locations = $locationService->loadLocations($content->contentInfo);
606
                    if (count($locations) > 1) {
607
                        $otherParentLocations = array();
608
                        foreach($locations as $otherLocation) {
609
                            if ($otherLocation->id != $location->id) {
610
                                $otherParentLocations[] = $otherLocation->parentLocationId;
611
                            }
612
                        }
613
                        $contentData['other_parent_locations'] = $otherParentLocations;
614
                    }
615
                    break;
616 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...
617
                    $contentData = array_merge(
618
                        $contentData,
619
                        array(
620
                            'match' => array(
621
                                ContentMatcher::MATCH_CONTENT_REMOTE_ID => $content->contentInfo->remoteId
622
                            ),
623
                            'new_remote_id' => $content->contentInfo->remoteId,
624
                        )
625
                    );
626
                    break;
627 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...
628
                    $contentData = array_merge(
629
                        $contentData,
630
                        array(
631
                            'match' => array(
632
                                ContentMatcher::MATCH_CONTENT_REMOTE_ID => $content->contentInfo->remoteId
633
                            )
634
                        )
635
                    );
636
                    break;
637
                default:
638
                    throw new \Exception("Executor 'content' doesn't support mode '$mode'");
639
            }
640
641
            if ($mode != 'delete') {
642
643
                $attributes = array();
644
                foreach ($content->getFieldsByLanguage($this->getLanguageCode()) as $fieldIdentifier => $field) {
645
                    $fieldDefinition = $contentType->getFieldDefinition($fieldIdentifier);
646
                    $attributes[$field->fieldDefIdentifier] = $this->complexFieldManager->fieldValueToHash(
647
                        $fieldDefinition->fieldTypeIdentifier, $contentType->identifier, $field->value
648
                    );
649
                }
650
651
                $contentData = array_merge(
652
                    $contentData,
653
                    array(
654
                        'lang' => $this->getLanguageCode(),
655
                        'section' => $content->contentInfo->sectionId,
656
                        'owner' => $content->contentInfo->ownerId,
657
                        'modification_date' => $content->contentInfo->modificationDate->getTimestamp(),
658
                        'publication_date' => $content->contentInfo->publishedDate->getTimestamp(),
659
                        'always_available' => (bool)$content->contentInfo->alwaysAvailable,
660
                        'attributes' => $attributes
661
                    )
662
                );
663
            }
664
665
            $data[] = $contentData;
666
        }
667
668
        $this->loginUser($previousUserId);
669
        return $data;
670
    }
671
}
672