Completed
Push — master ( ba8e0e...592b66 )
by Gaetano
08:41
created

ContentManager::toDateTime()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 6
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\FieldType\Checkbox\Value as CheckboxValue;
11
use Kaliop\eZMigrationBundle\API\Collection\ContentCollection;
12
use Kaliop\eZMigrationBundle\Core\Matcher\ContentMatcher;
13
use Kaliop\eZMigrationBundle\Core\Matcher\SectionMatcher;
14
use Kaliop\eZMigrationBundle\Core\Matcher\UserMatcher;
15
use Kaliop\eZMigrationBundle\Core\Matcher\ObjectStateMatcher;
16
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
17
18
/**
19
 * Implements the actions for managing (create/update/delete) Content in the system through
20
 * migrations and abstracts away the eZ Publish Public API.
21
 *
22
 * @todo add support for updating of content metadata
23
 */
24
class ContentManager extends RepositoryExecutor
25
{
26
    protected $supportedStepTypes = array('content');
27 20
28
    protected $contentMatcher;
29 20
    protected $sectionMatcher;
30 20
    protected $userMatcher;
31 20
    protected $objectStateMatcher;
32
    protected $complexFieldManager;
33
    protected $locationManager;
34
35
    public function __construct(ContentMatcher $contentMatcher, SectionMatcher $sectionMatcher, UserMatcher $userMatcher,
36 4
        ObjectStateMatcher $objectStateMatcher, $complexFieldManager, $locationManager)
37
    {
38 4
        $this->contentMatcher = $contentMatcher;
39 4
        $this->sectionMatcher = $sectionMatcher;
40 4
        $this->userMatcher = $userMatcher;
41
        $this->objectStateMatcher = $objectStateMatcher;
42 4
        $this->complexFieldManager = $complexFieldManager;
43 4
        $this->locationManager = $locationManager;
44 3
    }
45 3
46 4
    /**
47
     * Handle the content create migration action type
48 4
     */
49 4
    protected function create()
50
    {
51 4
        $contentService = $this->repository->getContentService();
52 1
        $locationService = $this->repository->getLocationService();
53 1
        $contentTypeService = $this->repository->getContentTypeService();
54
55
        $contentTypeIdentifier = $this->dsl['content_type'];
56 4
        $contentTypeIdentifier = $this->referenceResolver->resolveReference($contentTypeIdentifier);
57 4
        /// @todo use a contenttypematcher
58 1
        $contentType = $contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier);
59 1
60 4
        $contentCreateStruct = $contentService->newContentCreateStruct($contentType, $this->getLanguageCode());
61 4
62 1
        $this->setFields($contentCreateStruct, $this->dsl['attributes'], $contentType);
63 1
64
        if (isset($this->dsl['remote_id'])) {
65 4
            $contentCreateStruct->remoteId = $this->dsl['remote_id'];
66 1
        }
67 1
68
        if (isset($this->dsl['section'])) {
69 4
            $sectionId = $this->dsl['section'];
70
            $sectionId = $this->referenceResolver->resolveReference($sectionId);
71 4
            $contentCreateStruct->sectionId = $sectionId;
72
        }
73
74 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...
75
            $owner = $this->getUser($this->dsl['owner']);
76
            $contentCreateStruct->ownerId = $owner->id;
77
        }
78
79
        // This is a bit tricky, as the eZPublish API does not support having a different creator and owner with only 1 version.
80
        // We allow it, hoping that nothing gets broken because of it
81
        if (isset($this->dsl['version_creator'])) {
82
            $realContentOwnerId = $contentCreateStruct->ownerId;
83 4
            if ($realContentOwnerId == null) {
84 3
                $realContentOwnerId = $this->repository->getCurrentUser()->id;
85
            }
86 3
            $versionCreator = $this->getUser($this->dsl['version_creator']);
87 3
            $contentCreateStruct->ownerId = $versionCreator->id;
88
        }
89
90
        if (isset($this->dsl['modification_date'])) {
91
            $contentCreateStruct->modificationDate = $this->toDateTime($this->dsl['modification_date']);
92
        }
93
94 1
        // instantiate a location create struct from the parent location:
95
        // BC
96 1
        $locationId = isset($this->dsl['parent_location']) ? $this->dsl['parent_location'] : (
97 1
            isset($this->dsl['main_location']) ? $this->dsl['main_location'] : null
98
        );
99
        // 1st resolve references
100
        $locationId = $this->referenceResolver->resolveReference($locationId);
101
        // 2nd allow to specify the location via remote_id
102
        $locationId = $this->locationManager->matchLocationByKey($locationId)->id;
103
        $locationCreateStruct = $locationService->newLocationCreateStruct($locationId);
104
105
        if (isset($this->dsl['location_remote_id'])) {
106
            $locationCreateStruct->remoteId = $this->dsl['location_remote_id'];
107
        }
108
109
        if (isset($this->dsl['priority'])) {
110
            $locationCreateStruct->priority = $this->dsl['priority'];
111
        }
112
113
        if (isset($this->dsl['is_hidden'])) {
114
            $locationCreateStruct->hidden = $this->dsl['is_hidden'];
115
        }
116
117
        if (isset($this->dsl['sort_field'])) {
118
            $locationCreateStruct->sortField = $this->locationManager->getSortField($this->dsl['sort_field']);
119
        } else {
120
            $locationCreateStruct->sortField = $contentType->defaultSortField;
121 1
        }
122
123 1
        if (isset($this->dsl['sort_order'])) {
124
            $locationCreateStruct->sortOrder = $this->locationManager->getSortOrder($this->dsl['sort_order']);
125
        } else {
126
            $locationCreateStruct->sortOrder = $contentType->defaultSortOrder;
127 1
        }
128
129 1
        $locations = array($locationCreateStruct);
130 1
131
        // BC
132 1
        $other_locations = isset($this->dsl['other_parent_locations']) ? $this->dsl['other_parent_locations'] : (
133 1
            isset($this->dsl['other_locations']) ? $this->dsl['other_locations'] : null
134 1
        );
135
        if (isset($other_locations)) {
136 1
            foreach ($other_locations as $locationId) {
137
                $locationId = $this->referenceResolver->resolveReference($locationId);
138 1
                $locationId = $this->locationManager->matchLocationByKey($locationId)->id;
139 1
                $secondaryLocationCreateStruct = $locationService->newLocationCreateStruct($locationId);
140 1
                array_push($locations, $secondaryLocationCreateStruct);
141
            }
142 1
        }
143 1
144 1
        // create a draft using the content and location create struct and publish it
145
        $draft = $contentService->createContent($contentCreateStruct, $locations);
146 1
        $content = $contentService->publishVersion($draft->versionInfo);
147
148
        if (isset($this->dsl['object_states'])) {
149
            $this->setObjectStates($content, $this->dsl['object_states']);
150
        }
151
152
        // 2nd part of the hack: re-set the content owner to its intended value
153
        if (isset($this->dsl['version_creator']) || isset($this->dsl['publication_date'])) {
154
            $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
155
156
            if (isset($this->dsl['version_creator'])) {
157
                $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...
158
            }
159
            if (isset($this->dsl['publication_date'])) {
160
                $contentMetaDataUpdateStruct->publishedDate = $this->toDateTime($this->dsl['publication_date']);
161 1
            }
162 1
163 1
            $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct);
164
        }
165
166
        $this->setReferences($content);
167
168 3
        return $content;
169
    }
170 3
171
    /**
172 3
     * Handle the content update migration action type
173
     *
174 3
     * @todo handle updating of more metadata fields
175
     */
176 3
    protected function update()
177 3
    {
178
        $contentService = $this->repository->getContentService();
179
        $contentTypeService = $this->repository->getContentTypeService();
180
181 3
        $contentCollection = $this->matchContents('update');
182 3
183
        if (count($contentCollection) > 1 && isset($this->dsl['references'])) {
184
            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");
185
        }
186
187
        $contentType = null;
188
189 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...
190
            $contentInfo = $content->contentInfo;
191 3
192
            if ($contentType == null) {
193
                $contentType = $contentTypeService->loadContentType($contentInfo->contentTypeId);
194
            }
195
196 3
            $contentUpdateStruct = $contentService->newContentUpdateStruct();
197 1
198 1
            if (isset($this->dsl['attributes'])) {
199 1
                $this->setFields($contentUpdateStruct, $this->dsl['attributes'], $contentType);
200
            }
201
202 1
            $versionCreator = null;
203
            if (isset($this->dsl['version_creator'])) {
204 3
                $versionCreator = $this->getUser($this->dsl['version_creator']);
205
            }
206
207 3
            $draft = $contentService->createContentDraft($contentInfo, null, $versionCreator);
208 3
            $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
209 1
            $content = $contentService->publishVersion($draft->versionInfo);
210 1
211 1
            if (isset($this->dsl['new_remote_id']) || isset($this->dsl['new_remote_id']) ||
212 1
                isset($this->dsl['modification_date']) || isset($this->dsl['publication_date'])) {
213 1
214 1
                $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct();
215 3
216 3
                if (isset($this->dsl['new_remote_id'])) {
217 3
                    $contentMetaDataUpdateStruct->remoteId = $this->dsl['new_remote_id'];
218
                }
219 3
220 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...
221 3
                    $owner = $this->getUser($this->dsl['owner']);
222
                    $contentMetaDataUpdateStruct->ownerId = $owner->id;
223
                }
224
225
                if (isset($this->dsl['modification_date'])) {
226
                    $contentMetaDataUpdateStruct->modificationDate = $this->toDateTime($this->dsl['modification_date']);
227
                }
228
229
                if (isset($this->dsl['publication_date'])) {
230
                    $contentMetaDataUpdateStruct->publishedDate = $this->toDateTime($this->dsl['publication_date']);
231 4
                }
232
233 4
                $content = $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct);
234
            }
235
236 4
            if (isset($this->dsl['section'])) {
237
                $this->setSection($content, $this->dsl['section']);
238 4
            }
239
240 4
            if (isset($this->dsl['object_states'])) {
241
                $this->setObjectStates($content, $this->dsl['object_states']);
242 1
            }
243 1
244
            $contentCollection[$key] = $content;
245 4
        }
246 3
247
        $this->setReferences($contentCollection);
0 ignored issues
show
Bug introduced by
It seems like $contentCollection defined by $this->matchContents('update') on line 181 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...
248 4
249 4
        return $contentCollection;
250 4
    }
251
252
    /**
253
     * Handle the content delete migration action type
254
     */
255
    protected function delete()
256
    {
257
        $contentService = $this->repository->getContentService();
258
259
        $contentCollection = $this->matchContents('delete');
260
261
        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...
262 4
            try {
263
                $contentService->deleteContent($content->contentInfo);
264 4
            } catch (NotFoundException $e) {
265 4
                // Someone else (or even us, by virtue of location tree?) removed the content which we found just a
266
                // second ago. We can safely ignore this
267
            }
268 4
        }
269 4
270 4
        return $contentCollection;
271
    }
272 4
273 1
    /**
274 1
     * @param string $action
275
     * @return ContentCollection
276 4
     * @throws \Exception
277
     */
278 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...
279
    {
280
        if (!isset($this->dsl['object_id']) && !isset($this->dsl['remote_id']) && !isset($this->dsl['match'])) {
281
            throw new \Exception("The ID or remote ID of an object or a Match Condition is required to $action a new location.");
282
        }
283
284
        // Backwards compat
285
        if (!isset($this->dsl['match'])) {
286
            if (isset($this->dsl['object_id'])) {
287 1
                $this->dsl['match'] = array('content_id' => $this->dsl['object_id']);
288
            } elseif (isset($this->dsl['remote_id'])) {
289 1
                $this->dsl['match'] = array('content_remote_id' => $this->dsl['remote_id']);
290 1
            }
291
        }
292
293
        $match = $this->dsl['match'];
294
295
        // convert the references passed in the match
296
        foreach ($match as $condition => $values) {
297
            if (is_array($values)) {
298
                foreach ($values as $position => $value) {
299
                    $match[$condition][$position] = $this->referenceResolver->resolveReference($value);
300
                }
301
            } else {
302 3
                $match[$condition] = $this->referenceResolver->resolveReference($values);
303
            }
304 3
        }
305 2
306
        return $this->contentMatcher->match($match);
307
    }
308 3
309
    /**
310
     * Helper function to set the fields of a ContentCreateStruct based on the DSL attribute settings.
311
     *
312
     * @param ContentCreateStruct|ContentUpdateStruct $createOrUpdateStruct
313
     * @param ContentType $contentType
314
     * @param array $fields see description of expected format in code below
315 3
     * @throws \Exception
316
     */
317 3
    protected function setFields($createOrUpdateStruct, array $fields, ContentType $contentType)
318 3
    {
319 3
        $i = 0;
320 3
        // the 'easy' yml: key = field name, value = value
321 3
        // deprecated: the 'legacy' yml: key = numerical index, value = array ( field name => value )
322 3
        foreach ($fields as $key => $field) {
323 2
324 2
            if ($key === $i && is_array($field) && count($field) == 1) {
325 2
                // each $field is one key value pair
326 1
                // 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...
327 1
                reset($field);
328 1
                $fieldIdentifier = key($field);
329 1
                $fieldValue = $field[$fieldIdentifier];
330
            } else {
331
                $fieldIdentifier = $key;
332
                $fieldValue = $field;
333 3
            }
334
335 3
            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...
336 3
                throw new \Exception("Field '$fieldIdentifier' is not present in field type '{$contentType->identifier}'");
337
            }
338 3
339
            $fieldType = $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...
340
            $fieldValue = $this->getFieldValue($fieldValue, $fieldType, $contentType->identifier, $this->context);
341
342
            $createOrUpdateStruct->setField($fieldIdentifier, $fieldValue, $this->getLanguageCode());
343
344
            $i++;
345
        }
346
    }
347
348
    protected function setSection(Content $content, $sectionKey)
349
    {
350
        $sectionKey = $this->referenceResolver->resolveReference($sectionKey);
351
        $section = $this->sectionMatcher->matchOneByKey($sectionKey);
352
353
        $sectionService = $this->repository->getSectionService();
354
        $sectionService->assignSection($content->contentInfo, $section);
355
    }
356
357
    protected function setObjectStates(Content $content, array $stateKeys)
358
    {
359
        foreach($stateKeys as $stateKey) {
360
            $stateKey = $this->referenceResolver->resolveReference($stateKey);
361
            /** @var \eZ\Publish\API\Repository\Values\ObjectState\ObjectState $state */
362
            $state = $this->objectStateMatcher->matchOneByKey($stateKey);
363
364
            $stateService = $this->repository->getObjectStateService();
365
            $stateService->setContentState($content->contentInfo, $state->getObjectStateGroup(), $state);
366
        }
367
    }
368
369
    /**
370
     * Create the field value for either a primitive (ie. scalar) or complex field
371
     *
372
     * @param mixed $value
373
     * @param FieldDefinition $fieldDefinition
374
     * @param string $contentTypeIdentifier
375
     * @param array $context
376
     * @throws \InvalidArgumentException
377
     * @return mixed
378
     */
379
    protected function getFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array())
380
    {
381
        $fieldTypeIdentifier = $fieldDefinition->fieldTypeIdentifier;
382
        if (is_array($value) || $this->complexFieldManager->managesField($fieldTypeIdentifier, $contentTypeIdentifier)) {
383
            return $this->complexFieldManager->getComplexFieldValue($fieldTypeIdentifier, $contentTypeIdentifier, $value, $context);
384
        }
385
386
        return $this->getSingleFieldValue($value, $fieldDefinition, $contentTypeIdentifier, $context);
387
    }
388
389
    /**
390
     * Create the field value for a primitive field
391
     * This function is needed to get past validation on Checkbox fieldtype (eZP bug)
392
     *
393
     * @param mixed $value
394
     * @param FieldDefinition $fieldDefinition
395
     * @param string $contentTypeIdentifier
396
     * @param array $context
397
     * @throws \InvalidArgumentException
398
     * @return mixed
399
     */
400
    protected function getSingleFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array())
0 ignored issues
show
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...
401
    {
402
        $fieldTypeIdentifier = $fieldDefinition->fieldTypeIdentifier;
403
        switch ($fieldTypeIdentifier) {
404
            case 'ezboolean':
405
                $value = new CheckboxValue(($value == 1) ? true : false);
406
                break;
407
            default:
408
                // do nothing
409
        }
410
411
        // q: do we really want this to happen by default on all scalar field values?
412
        // Note: if you want this *not* to happen, register a complex field for your scalar field...
413
        $value = $this->referenceResolver->resolveReference($value);
414
415
        return $value;
416
    }
417
418
    /**
419
     * Load user using either login, email, id - resolving eventual references
420
     * @param int|string $userKey
421
     * @return \eZ\Publish\API\Repository\Values\User\User
422
     */
423
    protected function getUser($userKey)
424
    {
425
        $userKey = $this->referenceResolver->resolveReference($userKey);
426
        return $this->userMatcher->matchOneByKey($userKey);
427
    }
428
429
    /**
430
     * Sets references to certain content attributes.
431
     * The Content Manager currently supports setting references to object_id and location_id
432
     *
433
     * @param \eZ\Publish\API\Repository\Values\Content\Content|ContentCollection $content
434
     * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute
435
     * @return boolean
436
     *
437
     * @todo add support for other attributes: remote ids, contentTypeId, contentTypeIdentifier, section, etc...
438
     */
439
    protected function setReferences($content)
440
    {
441
        if (!array_key_exists('references', $this->dsl)) {
442
            return false;
443
        }
444
445
        if ($content instanceof ContentCollection) {
446
            if (count($content) > 1) {
447
                throw new \InvalidArgumentException('Content Manager does not support setting references for creating/updating of multiple contents');
448
            }
449
            $content = reset($content);
450
        }
451
452
        foreach ($this->dsl['references'] as $reference) {
453
454
            switch ($reference['attribute']) {
455
                case 'object_id':
456
                case 'content_id':
457
                case 'id':
458
                    $value = $content->id;
459
                    break;
460
                case 'remote_id':
461
                case 'content_remote_id':
462
                    $value = $content->contentInfo->remoteId;
463
                    break;
464
                case 'location_id':
465
                    $value = $content->contentInfo->mainLocationId;
466
                    break;
467
                case 'path':
468
                    $locationService = $this->repository->getLocationService();
469
                    $value = $locationService->loadLocation($content->contentInfo->mainLocationId)->pathString;
470
                    break;
471
                default:
472
                    throw new \InvalidArgumentException('Content Manager does not support setting references for attribute ' . $reference['attribute']);
473
            }
474
475
            $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...
476
        }
477
478
        return true;
479
    }
480
481
    /**
482
     * @param int|string $date if integer, we assume a timestamp
483
     * @return \DateTime
484
     */
485
    protected function toDateTime($date)
486
    {
487
        if (is_int($date)) {
488
            return new \DateTime("@".$date);
489
        } else {
490
            return new \DateTime($date);
491
        }
492
    }
493
}
494