Completed
Push — 6.1 ( 9744d5...08b231 )
by André
49:53 queued 22:16
created

DomainMapper   F

Complexity

Total Complexity 70

Size/Duplication

Total Lines 488
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 27

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 70
lcom 2
cbo 27
dl 0
loc 488
rs 1.3043
c 2
b 1
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A buildContentDomainObject() 0 15 2
C buildDomainFields() 0 54 15
A __construct() 0 13 1
B buildVersionInfoDomainObject() 0 37 5
B buildContentInfoDomainObject() 0 24 3
B buildRelationDomainObject() 0 28 4
B buildLocationDomainObject() 0 44 2
C buildSPILocationCreateStruct() 0 63 16
B isValidLocationSortField() 0 20 13
A isValidLocationSortOrder() 0 10 3
A validateTranslatedList() 0 14 4
A getDateTime() 0 7 1
A getUniqueHash() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like DomainMapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DomainMapper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * File containing the DomainMapper class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 *
9
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\Repository\Helper;
12
13
use eZ\Publish\SPI\Persistence\Content\Handler as ContentHandler;
14
use eZ\Publish\SPI\Persistence\Content\Location\Handler as LocationHandler;
15
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
16
use eZ\Publish\SPI\Persistence\Content\Type\Handler as TypeHandler;
17
use eZ\Publish\Core\Repository\Values\Content\Content;
18
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
19
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
20
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
21
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
22
use eZ\Publish\API\Repository\Values\Content\Field;
23
use eZ\Publish\Core\Repository\Values\Content\Relation;
24
use eZ\Publish\API\Repository\Values\Content\Location as APILocation;
25
use eZ\Publish\Core\Repository\Values\Content\Location;
26
use eZ\Publish\SPI\Persistence\Content as SPIContent;
27
use eZ\Publish\SPI\Persistence\Content\Location as SPILocation;
28
use eZ\Publish\SPI\Persistence\Content\VersionInfo as SPIVersionInfo;
29
use eZ\Publish\SPI\Persistence\Content\ContentInfo as SPIContentInfo;
30
use eZ\Publish\SPI\Persistence\Content\Relation as SPIRelation;
31
use eZ\Publish\SPI\Persistence\Content\Type as SPIType;
32
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct as SPILocationCreateStruct;
33
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
34
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
35
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
36
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
37
use DateTime;
38
39
/**
40
 * DomainMapper is an internal service.
41
 *
42
 * @internal
43
 */
44
class DomainMapper
45
{
46
    /**
47
     * @var \eZ\Publish\SPI\Persistence\Content\Handler
48
     */
49
    protected $contentHandler;
50
51
    /**
52
     * @var \eZ\Publish\SPI\Persistence\Content\Location\Handler
53
     */
54
    protected $locationHandler;
55
56
    /**
57
     * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler
58
     */
59
    protected $contentTypeHandler;
60
61
    /**
62
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
63
     */
64
    protected $contentLanguageHandler;
65
66
    /**
67
     * @var FieldTypeRegistry
68
     */
69
    protected $fieldTypeRegistry;
70
71
    /**
72
     * Setups service with reference to repository.
73
     *
74
     * @param \eZ\Publish\SPI\Persistence\Content\Handler $contentHandler
75
     * @param \eZ\Publish\SPI\Persistence\Content\Location\Handler $locationHandler
76
     * @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler
77
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $contentLanguageHandler
78
     * @param FieldTypeRegistry $fieldTypeRegistry
79
     */
80
    public function __construct(
81
        ContentHandler $contentHandler,
82
        LocationHandler $locationHandler,
83
        TypeHandler $contentTypeHandler,
84
        LanguageHandler $contentLanguageHandler,
85
        FieldTypeRegistry $fieldTypeRegistry
86
    ) {
87
        $this->contentHandler = $contentHandler;
88
        $this->locationHandler = $locationHandler;
89
        $this->contentTypeHandler = $contentTypeHandler;
90
        $this->contentLanguageHandler = $contentLanguageHandler;
91
        $this->fieldTypeRegistry = $fieldTypeRegistry;
92
    }
93
94
    /**
95
     * Builds a Content domain object from value object returned from persistence.
96
     *
97
     * @param \eZ\Publish\SPI\Persistence\Content $spiContent
98
     * @param ContentType|SPIType $contentType
99
     * @param array|null $fieldLanguages Language codes to filter fields on
100
     * @param string|null $fieldAlwaysAvailableLanguage Language code fallback if a given field is not found in $fieldLanguages
101
     *
102
     * @return \eZ\Publish\Core\Repository\Values\Content\Content
103
     */
104
    public function buildContentDomainObject(SPIContent $spiContent, $contentType = null, array $fieldLanguages = null, $fieldAlwaysAvailableLanguage = null)
105
    {
106
        if ($contentType === null) {
107
            $contentType = $this->contentTypeHandler->load(
108
                $spiContent->versionInfo->contentInfo->contentTypeId
109
            );
110
        }
111
112
        return new Content(
113
            array(
114
                'internalFields' => $this->buildDomainFields($spiContent->fields, $contentType, $fieldLanguages, $fieldAlwaysAvailableLanguage),
115
                'versionInfo' => $this->buildVersionInfoDomainObject($spiContent->versionInfo),
116
            )
117
        );
118
    }
119
120
    /**
121
     * Returns an array of domain fields created from given array of SPI fields.
122
     *
123
     * @throws InvalidArgumentType On invalid $contentType
124
     *
125
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
126
     * @param ContentType|SPIType $contentType
127
     * @param array|null $languages Language codes to filter fields on
128
     * @param string|null $alwaysAvailableLanguage Language code fallback if a given field is not found in $languages
129
     *
130
     * @return array
131
     */
132
    public function buildDomainFields(array $spiFields, $contentType, array $languages = null, $alwaysAvailableLanguage = null)
133
    {
134
        if (!$contentType instanceof SPIType && !$contentType instanceof ContentType) {
135
            throw new InvalidArgumentType('$contentType', 'SPI ContentType | API ContentType');
136
        }
137
138
        $fieldIdentifierMap = array();
139
        foreach ($contentType->fieldDefinitions as $fieldDefinitions) {
140
            $fieldIdentifierMap[$fieldDefinitions->id] = $fieldDefinitions->identifier;
141
        }
142
143
        $fieldInFilterLanguagesMap = array();
144
        if ($languages !== null && $alwaysAvailableLanguage !== null) {
145
            foreach ($spiFields as $spiField) {
146
                if (in_array($spiField->languageCode, $languages)) {
147
                    $fieldInFilterLanguagesMap[$spiField->fieldDefinitionId] = true;
148
                }
149
            }
150
        }
151
152
        $fields = array();
153
        foreach ($spiFields as $spiField) {
154
            // We ignore fields in content not part of the content type
155
            if (!isset($fieldIdentifierMap[$spiField->fieldDefinitionId])) {
156
                continue;
157
            }
158
159
            if ($languages !== null && !in_array($spiField->languageCode, $languages)) {
160
                // If filtering is enabled we ignore fields in other languages then $fieldLanguages, if:
161
                if ($alwaysAvailableLanguage === null) {
162
                    // Ignore field if we don't have $alwaysAvailableLanguageCode fallback
163
                    continue;
164
                } elseif (!empty($fieldInFilterLanguagesMap[$spiField->fieldDefinitionId])) {
165
                    // Ignore field if it exists in one of the filtered languages
166
                    continue;
167
                } elseif ($spiField->languageCode !== $alwaysAvailableLanguage) {
168
                    // Also ignore if field is not in $alwaysAvailableLanguageCode
169
                    continue;
170
                }
171
            }
172
173
            $fields[] = new Field(
174
                array(
175
                    'id' => $spiField->id,
176
                    'value' => $this->fieldTypeRegistry->getFieldType($spiField->type)
177
                        ->fromPersistenceValue($spiField->value),
178
                    'languageCode' => $spiField->languageCode,
179
                    'fieldDefIdentifier' => $fieldIdentifierMap[$spiField->fieldDefinitionId],
180
                )
181
            );
182
        }
183
184
        return $fields;
185
    }
186
187
    /**
188
     * Builds a VersionInfo domain object from value object returned from persistence.
189
     *
190
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $spiVersionInfo
191
     *
192
     * @return \eZ\Publish\Core\Repository\Values\Content\VersionInfo
193
     */
194
    public function buildVersionInfoDomainObject(SPIVersionInfo $spiVersionInfo)
195
    {
196
        $languageCodes = array();
197
        foreach ($spiVersionInfo->languageIds as $languageId) {
198
            $languageCodes[] = $this->contentLanguageHandler->load($languageId)->languageCode;
199
        }
200
201
        // Map SPI statuses to API
202
        switch ($spiVersionInfo->status) {
203
            case SPIVersionInfo::STATUS_ARCHIVED:
204
                $status = APIVersionInfo::STATUS_ARCHIVED;
205
                break;
206
207
            case SPIVersionInfo::STATUS_PUBLISHED:
208
                $status = APIVersionInfo::STATUS_PUBLISHED;
209
                break;
210
211
            case SPIVersionInfo::STATUS_DRAFT:
212
            default:
213
                $status = APIVersionInfo::STATUS_DRAFT;
214
        }
215
216
        return new VersionInfo(
217
            array(
218
                'id' => $spiVersionInfo->id,
219
                'versionNo' => $spiVersionInfo->versionNo,
220
                'modificationDate' => $this->getDateTime($spiVersionInfo->modificationDate),
221
                'creatorId' => $spiVersionInfo->creatorId,
222
                'creationDate' => $this->getDateTime($spiVersionInfo->creationDate),
223
                'status' => $status,
224
                'initialLanguageCode' => $spiVersionInfo->initialLanguageCode,
225
                'languageCodes' => $languageCodes,
226
                'names' => $spiVersionInfo->names,
227
                'contentInfo' => $this->buildContentInfoDomainObject($spiVersionInfo->contentInfo),
228
            )
229
        );
230
    }
231
232
    /**
233
     * Builds a ContentInfo domain object from value object returned from persistence.
234
     *
235
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo $spiContentInfo
236
     *
237
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
238
     */
239
    public function buildContentInfoDomainObject(SPIContentInfo $spiContentInfo)
240
    {
241
        return new ContentInfo(
242
            array(
243
                'id' => $spiContentInfo->id,
244
                'contentTypeId' => $spiContentInfo->contentTypeId,
245
                'name' => $spiContentInfo->name,
246
                'sectionId' => $spiContentInfo->sectionId,
247
                'currentVersionNo' => $spiContentInfo->currentVersionNo,
248
                'published' => $spiContentInfo->isPublished,
249
                'ownerId' => $spiContentInfo->ownerId,
250
                'modificationDate' => $spiContentInfo->modificationDate == 0 ?
251
                    null :
252
                    $this->getDateTime($spiContentInfo->modificationDate),
253
                'publishedDate' => $spiContentInfo->publicationDate == 0 ?
254
                    null :
255
                    $this->getDateTime($spiContentInfo->publicationDate),
256
                'alwaysAvailable' => $spiContentInfo->alwaysAvailable,
257
                'remoteId' => $spiContentInfo->remoteId,
258
                'mainLanguageCode' => $spiContentInfo->mainLanguageCode,
259
                'mainLocationId' => $spiContentInfo->mainLocationId,
260
            )
261
        );
262
    }
263
264
    /**
265
     * Builds API Relation object from provided SPI Relation object.
266
     *
267
     * @param \eZ\Publish\SPI\Persistence\Content\Relation $spiRelation
268
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $sourceContentInfo
269
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContentInfo
270
     *
271
     * @return \eZ\Publish\API\Repository\Values\Content\Relation
272
     */
273
    public function buildRelationDomainObject(
274
        SPIRelation $spiRelation,
275
        ContentInfo $sourceContentInfo,
276
        ContentInfo $destinationContentInfo
277
    ) {
278
        $sourceFieldDefinitionIdentifier = null;
279
        if ($spiRelation->sourceFieldDefinitionId !== null) {
280
            $contentType = $this->contentTypeHandler->load($sourceContentInfo->contentTypeId);
281
            foreach ($contentType->fieldDefinitions as $fieldDefinition) {
282
                if ($fieldDefinition->id !== $spiRelation->sourceFieldDefinitionId) {
283
                    continue;
284
                }
285
286
                $sourceFieldDefinitionIdentifier = $fieldDefinition->identifier;
287
                break;
288
            }
289
        }
290
291
        return new Relation(
292
            array(
293
                'id' => $spiRelation->id,
294
                'sourceFieldDefinitionIdentifier' => $sourceFieldDefinitionIdentifier,
295
                'type' => $spiRelation->type,
296
                'sourceContentInfo' => $sourceContentInfo,
297
                'destinationContentInfo' => $destinationContentInfo,
298
            )
299
        );
300
    }
301
302
    /**
303
     * Builds domain location object from provided persistence location.
304
     *
305
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
306
     *
307
     * @return \eZ\Publish\API\Repository\Values\Content\Location
308
     */
309
    public function buildLocationDomainObject(SPILocation $spiLocation)
310
    {
311
        // TODO: this is hardcoded workaround for missing ContentInfo on root location
312
        if ($spiLocation->id == 1) {
313
            $legacyDateTime = $this->getDateTime(1030968000); //  first known commit of eZ Publish 3.x 
314
            $contentInfo = new ContentInfo(
315
                array(
316
                    'id' => 0,
317
                    'name' => 'Top Level Nodes',
318
                    'sectionId' => 1,
319
                    'mainLocationId' => 1,
320
                    'contentTypeId' => 1,
321
                    'currentVersionNo' => 1,
322
                    'published' => 1,
323
                    'ownerId' => 14, // admin user
324
                    'modificationDate' => $legacyDateTime,
325
                    'publishedDate' => $legacyDateTime,
326
                    'alwaysAvailable' => 1,
327
                    'remoteId' => null,
328
                    'mainLanguageCode' => 'eng-GB',
329
                )
330
            );
331
        } else {
332
            $contentInfo = $this->buildContentInfoDomainObject(
333
                $this->contentHandler->loadContentInfo($spiLocation->contentId)
334
            );
335
        }
336
337
        return new Location(
338
            array(
339
                'contentInfo' => $contentInfo,
340
                'id' => $spiLocation->id,
341
                'priority' => $spiLocation->priority,
342
                'hidden' => $spiLocation->hidden,
343
                'invisible' => $spiLocation->invisible,
344
                'remoteId' => $spiLocation->remoteId,
345
                'parentLocationId' => $spiLocation->parentId,
346
                'pathString' => $spiLocation->pathString,
347
                'depth' => $spiLocation->depth,
348
                'sortField' => $spiLocation->sortField,
349
                'sortOrder' => $spiLocation->sortOrder,
350
            )
351
        );
352
    }
353
354
    /**
355
     * Creates an array of SPI location create structs from given array of API location create structs.
356
     *
357
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
358
     *
359
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
360
     * @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
361
     * @param mixed $mainLocation
362
     * @param mixed $contentId
363
     * @param mixed $contentVersionNo
364
     *
365
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct
366
     */
367
    public function buildSPILocationCreateStruct(
368
        $locationCreateStruct,
369
        APILocation $parentLocation,
370
        $mainLocation,
371
        $contentId,
372
        $contentVersionNo
373
    ) {
374
        if ($locationCreateStruct->priority !== null && !is_int($locationCreateStruct->priority)) {
375
            throw new InvalidArgumentValue('priority', $locationCreateStruct->priority, 'LocationCreateStruct');
376
        }
377
378
        if (!is_bool($locationCreateStruct->hidden)) {
379
            throw new InvalidArgumentValue('hidden', $locationCreateStruct->hidden, 'LocationCreateStruct');
380
        }
381
382
        if ($locationCreateStruct->remoteId !== null && (!is_string($locationCreateStruct->remoteId) || empty($locationCreateStruct->remoteId))) {
383
            throw new InvalidArgumentValue('remoteId', $locationCreateStruct->remoteId, 'LocationCreateStruct');
384
        }
385
386
        if ($locationCreateStruct->sortField !== null && !$this->isValidLocationSortField($locationCreateStruct->sortField)) {
387
            throw new InvalidArgumentValue('sortField', $locationCreateStruct->sortField, 'LocationCreateStruct');
388
        }
389
390
        if ($locationCreateStruct->sortOrder !== null && !$this->isValidLocationSortOrder($locationCreateStruct->sortOrder)) {
391
            throw new InvalidArgumentValue('sortOrder', $locationCreateStruct->sortOrder, 'LocationCreateStruct');
392
        }
393
394
        $remoteId = $locationCreateStruct->remoteId;
395
        if (null === $remoteId) {
396
            $remoteId = $this->getUniqueHash($locationCreateStruct);
397
        } else {
398
            try {
399
                $this->locationHandler->loadByRemoteId($remoteId);
400
                throw new InvalidArgumentException(
401
                    '$locationCreateStructs',
402
                    "Another Location with remoteId '{$remoteId}' exists"
403
                );
404
            } catch (NotFoundException $e) {
405
                // Do nothing
406
            }
407
        }
408
409
        return new SPILocationCreateStruct(
410
            array(
411
                'priority' => $locationCreateStruct->priority,
412
                'hidden' => $locationCreateStruct->hidden,
413
                // If we declare the new Location as hidden, it is automatically invisible
414
                // Otherwise it picks up visibility from parent Location
415
                // Note: There is no need to check for hidden status of parent, as hidden Location
416
                // is always invisible as well
417
                'invisible' => ($locationCreateStruct->hidden === true || $parentLocation->invisible),
418
                'remoteId' => $remoteId,
419
                'contentId' => $contentId,
420
                'contentVersion' => $contentVersionNo,
421
                // pathIdentificationString will be set in storage
422
                'pathIdentificationString' => null,
423
                'mainLocationId' => $mainLocation,
424
                'sortField' => $locationCreateStruct->sortField !== null ? $locationCreateStruct->sortField : Location::SORT_FIELD_NAME,
425
                'sortOrder' => $locationCreateStruct->sortOrder !== null ? $locationCreateStruct->sortOrder : Location::SORT_ORDER_ASC,
426
                'parentId' => $locationCreateStruct->parentLocationId,
427
            )
428
        );
429
    }
430
431
    /**
432
     * Checks if given $sortField value is one of the defined sort field constants.
433
     *
434
     * @param mixed $sortField
435
     *
436
     * @return bool
437
     */
438
    public function isValidLocationSortField($sortField)
439
    {
440
        switch ($sortField) {
441
            case APILocation::SORT_FIELD_PATH:
442
            case APILocation::SORT_FIELD_PUBLISHED:
443
            case APILocation::SORT_FIELD_MODIFIED:
444
            case APILocation::SORT_FIELD_SECTION:
445
            case APILocation::SORT_FIELD_DEPTH:
446
            case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
447
            case APILocation::SORT_FIELD_CLASS_NAME:
448
            case APILocation::SORT_FIELD_PRIORITY:
449
            case APILocation::SORT_FIELD_NAME:
450
            case APILocation::SORT_FIELD_MODIFIED_SUBNODE:
0 ignored issues
show
Deprecated Code introduced by
The constant eZ\Publish\API\Repositor..._FIELD_MODIFIED_SUBNODE has been deprecated.

This class constant has been deprecated.

Loading history...
451
            case APILocation::SORT_FIELD_NODE_ID:
452
            case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
453
                return true;
454
        }
455
456
        return false;
457
    }
458
459
    /**
460
     * Checks if given $sortOrder value is one of the defined sort order constants.
461
     *
462
     * @param mixed $sortOrder
463
     *
464
     * @return bool
465
     */
466
    public function isValidLocationSortOrder($sortOrder)
467
    {
468
        switch ($sortOrder) {
469
            case APILocation::SORT_ORDER_DESC:
470
            case APILocation::SORT_ORDER_ASC:
471
                return true;
472
        }
473
474
        return false;
475
    }
476
477
    /**
478
     * Validates given translated list $list, which should be an array of strings with language codes as keys.
479
     *
480
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
481
     *
482
     * @param mixed $list
483
     * @param string $argumentName
484
     */
485
    public function validateTranslatedList($list, $argumentName)
486
    {
487
        if (!is_array($list)) {
488
            throw new InvalidArgumentType($argumentName, 'array', $list);
489
        }
490
491
        foreach ($list as $languageCode => $translation) {
492
            $this->contentLanguageHandler->loadByLanguageCode($languageCode);
493
494
            if (!is_string($translation)) {
495
                throw new InvalidArgumentType($argumentName . "['$languageCode']", 'string', $translation);
496
            }
497
        }
498
    }
499
500
    /**
501
     * Returns \DateTime object from given $timestamp in environment timezone.
502
     *
503
     * This method is needed because constructing \DateTime with $timestamp will
504
     * return the object in UTC timezone.
505
     *
506
     * @param int $timestamp
507
     *
508
     * @return \DateTime
509
     */
510
    public function getDateTime($timestamp)
511
    {
512
        $dateTime = new DateTime();
513
        $dateTime->setTimestamp($timestamp);
514
515
        return $dateTime;
516
    }
517
518
    /**
519
     * Creates unique hash string for given $object.
520
     *
521
     * Used for remoteId.
522
     *
523
     * @param object $object
524
     *
525
     * @return string
526
     */
527
    public function getUniqueHash($object)
528
    {
529
        return md5(uniqid(get_class($object), true));
530
    }
531
}
532