Completed
Push — location_content_property ( b180d5 )
by André
16:28
created

DomainMapper   F

Complexity

Total Complexity 103

Size/Duplication

Total Lines 682
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 29

Importance

Changes 0
Metric Value
dl 0
loc 682
rs 1.0904
c 0
b 0
f 0
wmc 103
lcom 3
cbo 29

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
B buildContentDomainObject() 0 28 6
A buildContentProxy() 0 6 1
A buildContentProxyList() 0 10 2
A generatorForContentList() 0 17 3
C buildDomainFields() 0 55 15
B buildVersionInfoDomainObject() 0 42 6
B buildContentInfoDomainObject() 0 40 6
B buildRelationDomainObject() 0 28 4
B buildLocationDomainObject() 0 50 4
C buildContentDomainObjectsOnSearchResult() 0 34 8
B buildLocationDomainObjectsOnSearchResult() 0 33 6
C buildSPILocationCreateStruct() 0 63 15
B isValidLocationSortField() 0 20 13
A isValidLocationSortOrder() 0 10 3
A isValidLocationPriority() 0 8 4
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
namespace eZ\Publish\Core\Repository\Helper;
10
11
use eZ\Publish\API\Repository\Values\Content\Search\SearchResult;
12
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
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\Core\Repository\Values\Content\ContentProxy;
19
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
20
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
21
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
22
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
23
use eZ\Publish\API\Repository\Values\Content\Field;
24
use eZ\Publish\Core\Repository\Values\Content\Relation;
25
use eZ\Publish\API\Repository\Values\Content\Location as APILocation;
26
use eZ\Publish\Core\Repository\Values\Content\Location;
27
use eZ\Publish\SPI\Persistence\Content as SPIContent;
28
use eZ\Publish\SPI\Persistence\Content\Location as SPILocation;
29
use eZ\Publish\SPI\Persistence\Content\VersionInfo as SPIVersionInfo;
30
use eZ\Publish\SPI\Persistence\Content\ContentInfo as SPIContentInfo;
31
use eZ\Publish\SPI\Persistence\Content\Relation as SPIRelation;
32
use eZ\Publish\SPI\Persistence\Content\Type as SPIType;
33
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct as SPILocationCreateStruct;
34
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
35
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
36
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
37
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
38
use DateTime;
39
40
/**
41
 * DomainMapper is an internal service.
42
 *
43
 * @internal Meant for internal use by Repository.
44
 */
45
class DomainMapper
46
{
47
    const MAX_LOCATION_PRIORITY = 2147483647;
48
    const MIN_LOCATION_PRIORITY = -2147483648;
49
50
    /**
51
     * @var \eZ\Publish\SPI\Persistence\Content\Handler
52
     */
53
    protected $contentHandler;
54
55
    /**
56
     * @var \eZ\Publish\SPI\Persistence\Content\Location\Handler
57
     */
58
    protected $locationHandler;
59
60
    /**
61
     * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler
62
     */
63
    protected $contentTypeHandler;
64
65
    /**
66
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
67
     */
68
    protected $contentLanguageHandler;
69
70
    /**
71
     * @var FieldTypeRegistry
72
     */
73
    protected $fieldTypeRegistry;
74
75
    /**
76
     * Setups service with reference to repository.
77
     *
78
     * @param \eZ\Publish\SPI\Persistence\Content\Handler $contentHandler
79
     * @param \eZ\Publish\SPI\Persistence\Content\Location\Handler $locationHandler
80
     * @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler
81
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $contentLanguageHandler
82
     * @param FieldTypeRegistry $fieldTypeRegistry
83
     */
84
    public function __construct(
85
        ContentHandler $contentHandler,
86
        LocationHandler $locationHandler,
87
        TypeHandler $contentTypeHandler,
88
        LanguageHandler $contentLanguageHandler,
89
        FieldTypeRegistry $fieldTypeRegistry
90
    ) {
91
        $this->contentHandler = $contentHandler;
92
        $this->locationHandler = $locationHandler;
93
        $this->contentTypeHandler = $contentTypeHandler;
94
        $this->contentLanguageHandler = $contentLanguageHandler;
95
        $this->fieldTypeRegistry = $fieldTypeRegistry;
96
    }
97
98
    /**
99
     * Builds a Content domain object from value object returned from persistence.
100
     *
101
     * @param \eZ\Publish\SPI\Persistence\Content $spiContent
102
     * @param ContentType|SPIType $contentType
103
     * @param array|null $fieldLanguages Language codes to filter fields on
104
     * @param string|null $fieldAlwaysAvailableLanguage Language code fallback if a given field is not found in $fieldLanguages
105
     *
106
     * @return \eZ\Publish\Core\Repository\Values\Content\Content
107
     */
108
    public function buildContentDomainObject(SPIContent $spiContent, $contentType = null, array $fieldLanguages = null, string $fieldAlwaysAvailableLanguage = null)
109
    {
110
        if ($contentType === null) {
111
            $contentType = $this->contentTypeHandler->load(
112
                $spiContent->versionInfo->contentInfo->contentTypeId
113
            );
114
        }
115
116
        $prioritizedFieldLanguageCode = null;
117
        $prioritizedLanguages = $fieldLanguages ?: [];
118
        if (!empty($prioritizedLanguages)) {
119
            $availableFieldLanguageMap = array_fill_keys($spiContent->versionInfo->languageCodes, true);
120
            foreach ($prioritizedLanguages as $prioritizedLanguage) {
121
                if (isset($availableFieldLanguageMap[$prioritizedLanguage])) {
122
                    $prioritizedFieldLanguageCode = $prioritizedLanguage;
123
                    break;
124
                }
125
            }
126
        }
127
128
        return new Content(
129
            array(
130
                'internalFields' => $this->buildDomainFields($spiContent->fields, $contentType, $fieldLanguages, $fieldAlwaysAvailableLanguage),
131
                'versionInfo' => $this->buildVersionInfoDomainObject($spiContent->versionInfo, $prioritizedLanguages),
132
                'prioritizedFieldLanguageCode' => $prioritizedFieldLanguageCode,
133
            )
134
        );
135
    }
136
137
    /**
138
     * Builds a Content proxy object (lazy loaded, loads as soon as used).
139
     */
140
    public function buildContentProxy(int $id, array $prioritizedLanguages = []): APIContent
141
    {
142
        $generator = $this->generatorForContentList([$id], $prioritizedLanguages);
143
144
        return new ContentProxy($generator, $id);
145
    }
146
147
    /**
148
     * Builds a list of Content proxy objects (lazy loaded, loads all as soon as one of them loads).
149
     */
150
    public function buildContentProxyList(array $ids, array $prioritizedLanguages = []): array
151
    {
152
        $list = [];
153
        $generator = $this->generatorForContentList($ids, $prioritizedLanguages);
154
        foreach ($ids as $id) {
155
            $list[$id] = new ContentProxy($generator, $id);
156
        }
157
158
        return $list;
159
    }
160
161
    private function generatorForContentList(array $ids, array $prioritizedLanguages = []): \Generator
162
    {
163
        $list = $this->contentHandler->loadContentList($ids, $prioritizedLanguages);
164
165
        while (!empty($list)) {
166
            $id = yield;
167
            $info = $list[$id]->versionInfo->contentInfo;
168
            yield $this->buildContentDomainObject(
169
                $list[$id],
170
                null,//@todo bulk load content type, AND(~/OR~) add in-memory cache for it which will also benefit all cases
171
                $prioritizedLanguages,
172
                $info->alwaysAvailable ? $info->mainLanguageCode : null
173
            );
174
175
            unset($list[$id]);
176
        }
177
    }
178
179
    /**
180
     * Returns an array of domain fields created from given array of SPI fields.
181
     *
182
     * @throws InvalidArgumentType On invalid $contentType
183
     *
184
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
185
     * @param ContentType|SPIType $contentType
186
     * @param array $languages A language priority, filters returned fields and is used as prioritized language code on
187
     *                         returned value object. If not given all languages are returned.
188
     * @param string|null $alwaysAvailableLanguage Language code fallback if a given field is not found in $languages
189
     *
190
     * @return array
191
     */
192
    public function buildDomainFields(array $spiFields, $contentType, array $languages = null, string $alwaysAvailableLanguage = null)
193
    {
194
        if (!$contentType instanceof SPIType && !$contentType instanceof ContentType) {
195
            throw new InvalidArgumentType('$contentType', 'SPI ContentType | API ContentType');
196
        }
197
198
        $fieldIdentifierMap = array();
199
        foreach ($contentType->fieldDefinitions as $fieldDefinitions) {
200
            $fieldIdentifierMap[$fieldDefinitions->id] = $fieldDefinitions->identifier;
201
        }
202
203
        $fieldInFilterLanguagesMap = array();
204
        if ($languages !== null && $alwaysAvailableLanguage !== null) {
205
            foreach ($spiFields as $spiField) {
206
                if (in_array($spiField->languageCode, $languages)) {
207
                    $fieldInFilterLanguagesMap[$spiField->fieldDefinitionId] = true;
208
                }
209
            }
210
        }
211
212
        $fields = array();
213
        foreach ($spiFields as $spiField) {
214
            // We ignore fields in content not part of the content type
215
            if (!isset($fieldIdentifierMap[$spiField->fieldDefinitionId])) {
216
                continue;
217
            }
218
219
            if ($languages !== null && !in_array($spiField->languageCode, $languages)) {
220
                // If filtering is enabled we ignore fields in other languages then $fieldLanguages, if:
221
                if ($alwaysAvailableLanguage === null) {
222
                    // Ignore field if we don't have $alwaysAvailableLanguageCode fallback
223
                    continue;
224
                } elseif (!empty($fieldInFilterLanguagesMap[$spiField->fieldDefinitionId])) {
225
                    // Ignore field if it exists in one of the filtered languages
226
                    continue;
227
                } elseif ($spiField->languageCode !== $alwaysAvailableLanguage) {
228
                    // Also ignore if field is not in $alwaysAvailableLanguageCode
229
                    continue;
230
                }
231
            }
232
233
            $fields[] = new Field(
234
                array(
235
                    'id' => $spiField->id,
236
                    'value' => $this->fieldTypeRegistry->getFieldType($spiField->type)
237
                        ->fromPersistenceValue($spiField->value),
238
                    'languageCode' => $spiField->languageCode,
239
                    'fieldDefIdentifier' => $fieldIdentifierMap[$spiField->fieldDefinitionId],
240
                    'fieldTypeIdentifier' => $spiField->type,
241
                )
242
            );
243
        }
244
245
        return $fields;
246
    }
247
248
    /**
249
     * Builds a VersionInfo domain object from value object returned from persistence.
250
     *
251
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $spiVersionInfo
252
     * @param array $prioritizedLanguages
253
     *
254
     * @return \eZ\Publish\Core\Repository\Values\Content\VersionInfo
255
     */
256
    public function buildVersionInfoDomainObject(SPIVersionInfo $spiVersionInfo, array $prioritizedLanguages = [])
257
    {
258
        // Map SPI statuses to API
259
        switch ($spiVersionInfo->status) {
260
            case SPIVersionInfo::STATUS_ARCHIVED:
261
                $status = APIVersionInfo::STATUS_ARCHIVED;
262
                break;
263
264
            case SPIVersionInfo::STATUS_PUBLISHED:
265
                $status = APIVersionInfo::STATUS_PUBLISHED;
266
                break;
267
268
            case SPIVersionInfo::STATUS_DRAFT:
269
            default:
270
                $status = APIVersionInfo::STATUS_DRAFT;
271
        }
272
273
        // Find prioritised language among names
274
        $prioritizedNameLanguageCode = null;
275
        foreach ($prioritizedLanguages as $prioritizedLanguage) {
276
            if (isset($spiVersionInfo->names[$prioritizedLanguage])) {
277
                $prioritizedNameLanguageCode = $prioritizedLanguage;
278
                break;
279
            }
280
        }
281
282
        return new VersionInfo(
283
            array(
284
                'id' => $spiVersionInfo->id,
285
                'versionNo' => $spiVersionInfo->versionNo,
286
                'modificationDate' => $this->getDateTime($spiVersionInfo->modificationDate),
287
                'creatorId' => $spiVersionInfo->creatorId,
288
                'creationDate' => $this->getDateTime($spiVersionInfo->creationDate),
289
                'status' => $status,
290
                'initialLanguageCode' => $spiVersionInfo->initialLanguageCode,
291
                'languageCodes' => $spiVersionInfo->languageCodes,
292
                'names' => $spiVersionInfo->names,
293
                'contentInfo' => $this->buildContentInfoDomainObject($spiVersionInfo->contentInfo),
294
                'prioritizedNameLanguageCode' => $prioritizedNameLanguageCode,
295
            )
296
        );
297
    }
298
299
    /**
300
     * Builds a ContentInfo domain object from value object returned from persistence.
301
     *
302
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo $spiContentInfo
303
     *
304
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
305
     */
306
    public function buildContentInfoDomainObject(SPIContentInfo $spiContentInfo)
307
    {
308
        // Map SPI statuses to API
309
        switch ($spiContentInfo->status) {
310
            case SPIContentInfo::STATUS_TRASHED:
311
                $status = ContentInfo::STATUS_TRASHED;
312
                break;
313
314
            case SPIContentInfo::STATUS_PUBLISHED:
315
                $status = ContentInfo::STATUS_PUBLISHED;
316
                break;
317
318
            case SPIContentInfo::STATUS_DRAFT:
319
            default:
320
                $status = ContentInfo::STATUS_DRAFT;
321
        }
322
323
        return new ContentInfo(
324
            array(
325
                'id' => $spiContentInfo->id,
326
                'contentTypeId' => $spiContentInfo->contentTypeId,
327
                'name' => $spiContentInfo->name,
328
                'sectionId' => $spiContentInfo->sectionId,
329
                'currentVersionNo' => $spiContentInfo->currentVersionNo,
330
                'published' => $spiContentInfo->isPublished,
0 ignored issues
show
Deprecated Code introduced by
The property eZ\Publish\SPI\Persisten...ntentInfo::$isPublished has been deprecated with message: Use SPI\ContentInfo::$status (with value ContentInfo::STATUS_PUBLISHED) Flag indicating if content is currently published.

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

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

Loading history...
331
                'ownerId' => $spiContentInfo->ownerId,
332
                'modificationDate' => $spiContentInfo->modificationDate == 0 ?
333
                    null :
334
                    $this->getDateTime($spiContentInfo->modificationDate),
335
                'publishedDate' => $spiContentInfo->publicationDate == 0 ?
336
                    null :
337
                    $this->getDateTime($spiContentInfo->publicationDate),
338
                'alwaysAvailable' => $spiContentInfo->alwaysAvailable,
339
                'remoteId' => $spiContentInfo->remoteId,
340
                'mainLanguageCode' => $spiContentInfo->mainLanguageCode,
341
                'mainLocationId' => $spiContentInfo->mainLocationId,
342
                'status' => $status,
343
            )
344
        );
345
    }
346
347
    /**
348
     * Builds API Relation object from provided SPI Relation object.
349
     *
350
     * @param \eZ\Publish\SPI\Persistence\Content\Relation $spiRelation
351
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $sourceContentInfo
352
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContentInfo
353
     *
354
     * @return \eZ\Publish\API\Repository\Values\Content\Relation
355
     */
356
    public function buildRelationDomainObject(
357
        SPIRelation $spiRelation,
358
        ContentInfo $sourceContentInfo,
359
        ContentInfo $destinationContentInfo
360
    ) {
361
        $sourceFieldDefinitionIdentifier = null;
362
        if ($spiRelation->sourceFieldDefinitionId !== null) {
363
            $contentType = $this->contentTypeHandler->load($sourceContentInfo->contentTypeId);
364
            foreach ($contentType->fieldDefinitions as $fieldDefinition) {
365
                if ($fieldDefinition->id !== $spiRelation->sourceFieldDefinitionId) {
366
                    continue;
367
                }
368
369
                $sourceFieldDefinitionIdentifier = $fieldDefinition->identifier;
370
                break;
371
            }
372
        }
373
374
        return new Relation(
375
            array(
376
                'id' => $spiRelation->id,
377
                'sourceFieldDefinitionIdentifier' => $sourceFieldDefinitionIdentifier,
378
                'type' => $spiRelation->type,
379
                'sourceContentInfo' => $sourceContentInfo,
380
                'destinationContentInfo' => $destinationContentInfo,
381
            )
382
        );
383
    }
384
385
    /**
386
     * Builds domain location object from provided persistence location.
387
     *
388
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
389
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo|null $contentInfo
390
     * @param \eZ\Publish\API\Repository\Values\Content\Content|null $content
391
     *
392
     * @return \eZ\Publish\API\Repository\Values\Content\Location
393
     */
394
    public function buildLocationDomainObject(SPILocation $spiLocation, SPIContentInfo $contentInfo = null, APIContent $content = null)
395
    {
396
        // TODO: this is hardcoded workaround for missing ContentInfo on root location
397
        if ($spiLocation->id == 1) {
398
            $legacyDateTime = $this->getDateTime(1030968000); //  first known commit of eZ Publish 3.x
399
            $contentInfo = new ContentInfo(
400
                array(
401
                    'id' => 0,
402
                    'name' => 'Top Level Nodes',
403
                    'sectionId' => 1,
404
                    'mainLocationId' => 1,
405
                    'contentTypeId' => 1,
406
                    'currentVersionNo' => 1,
407
                    'published' => 1,
408
                    'ownerId' => 14, // admin user
409
                    'modificationDate' => $legacyDateTime,
410
                    'publishedDate' => $legacyDateTime,
411
                    'alwaysAvailable' => 1,
412
                    'remoteId' => null,
413
                    'mainLanguageCode' => 'eng-GB',
414
                )
415
            );
416
            // content is left as null in this case atm
417
        } else {
418
            $contentInfo = $this->buildContentInfoDomainObject(
419
                $contentInfo ?: $this->contentHandler->loadContentInfo($spiLocation->contentId)
420
            );
421
422
            if ($content === null) {
423
                $content = $this->buildContentProxy($spiLocation->contentId);
424
            }
425
        }
426
427
        return new Location(
428
            array(
429
                'content' => $content,
430
                'contentInfo' => $contentInfo,
431
                'id' => $spiLocation->id,
432
                'priority' => $spiLocation->priority,
433
                'hidden' => $spiLocation->hidden,
434
                'invisible' => $spiLocation->invisible,
435
                'remoteId' => $spiLocation->remoteId,
436
                'parentLocationId' => $spiLocation->parentId,
437
                'pathString' => $spiLocation->pathString,
438
                'depth' => $spiLocation->depth,
439
                'sortField' => $spiLocation->sortField,
440
                'sortOrder' => $spiLocation->sortOrder,
441
            )
442
        );
443
    }
444
445
    /**
446
     * Build API Content domain objects in bulk and apply to ContentSearchResult.
447
     *
448
     * Loading of Content objects are done in one operation.
449
     *
450
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI ContentInfo items as hits
451
     * @param array $languageFilter
452
     *
453
     * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo[] ContentInfo we did not find content for is returned.
454
     */
455
    public function buildContentDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter)
456
    {
457
        if (empty($result->searchHits)) {
458
            return [];
459
        }
460
461
        $contentIds = [];
462
        foreach ($result->searchHits as $hit) {
463
            $contentIds[] = $hit->valueObject->id;
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
464
        }
465
466
        $contentList = $this->contentHandler->loadContentList(
467
            $contentIds,
468
            !empty($languageFilter['languages']) ? $languageFilter['languages'] : []
469
        );
470
471
        $missingContentList = [];
472
        foreach ($result->searchHits as $key => $hit) {
473
            if (isset($contentList[$hit->valueObject->id])) {
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
474
                $hit->valueObject = $this->buildContentDomainObject(
475
                    $contentList[$hit->valueObject->id],
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
476
                    null,//@todo bulk load content type, AND(~/OR~) add in-memory cache for it which will also benefit all cases
477
                    !empty($languageFilter['languages']) ? $languageFilter['languages'] : null,
478
                    empty($languageFilter['useAlwaysAvailable']) ? null : $hit->valueObject->mainLanguageCode
0 ignored issues
show
Documentation introduced by
The property mainLanguageCode does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
479
                );
480
            } else {
481
                $missingContentList[] = $hit->valueObject;
482
                unset($result->searchHits[$key]);
483
                --$result->totalCount;
484
            }
485
        }
486
487
        return $missingContentList;
488
    }
489
490
    /**
491
     * Build API Location and corresponding ContentInfo domain objects and apply to LocationSearchResult.
492
     *
493
     * Loading of ContentInfo objects are done in one operation.
494
     *
495
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI Location items as hits
496
     *
497
     * @return \eZ\Publish\SPI\Persistence\Content\Location[] Locations we did not find content info for is returned.
498
     */
499
    public function buildLocationDomainObjectsOnSearchResult(SearchResult $result, array $languageFilter)
500
    {
501
        if (empty($result->searchHits)) {
502
            return [];
503
        }
504
505
        $contentIds = [];
506
        foreach ($result->searchHits as $hit) {
507
            $contentIds[] = $hit->valueObject->contentId;
0 ignored issues
show
Documentation introduced by
The property contentId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
508
        }
509
510
        $missingLocations = [];
511
        $contentInfoList = $this->contentHandler->loadContentInfoList($contentIds);
512
        $contentList = $this->buildContentProxyList(
513
            $contentIds,
514
            !empty($languageFilter['languages']) ? $languageFilter['languages'] : []
515
        );
516
        foreach ($result->searchHits as $key => $hit) {
517
            if (isset($contentInfoList[$hit->valueObject->contentId])) {
0 ignored issues
show
Documentation introduced by
The property contentId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
518
                $hit->valueObject = $this->buildLocationDomainObject(
519
                    $hit->valueObject,
0 ignored issues
show
Compatibility introduced by
$hit->valueObject of type object<eZ\Publish\API\Re...ory\Values\ValueObject> is not a sub-type of object<eZ\Publish\SPI\Pe...tence\Content\Location>. It seems like you assume a child class of the class eZ\Publish\API\Repository\Values\ValueObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
520
                    $contentInfoList[$hit->valueObject->contentId],
0 ignored issues
show
Documentation introduced by
The property contentId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
521
                    $contentList[$hit->valueObject->contentId]
0 ignored issues
show
Documentation introduced by
The property contentId does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
522
                );
523
            } else {
524
                $missingLocations[] = $hit->valueObject;
525
                unset($result->searchHits[$key]);
526
                --$result->totalCount;
527
            }
528
        }
529
530
        return $missingLocations;
531
    }
532
533
    /**
534
     * Creates an array of SPI location create structs from given array of API location create structs.
535
     *
536
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
537
     *
538
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
539
     * @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
540
     * @param mixed $mainLocation
541
     * @param mixed $contentId
542
     * @param mixed $contentVersionNo
543
     *
544
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct
545
     */
546
    public function buildSPILocationCreateStruct(
547
        $locationCreateStruct,
548
        APILocation $parentLocation,
549
        $mainLocation,
550
        $contentId,
551
        $contentVersionNo
552
    ) {
553
        if (!$this->isValidLocationPriority($locationCreateStruct->priority)) {
554
            throw new InvalidArgumentValue('priority', $locationCreateStruct->priority, 'LocationCreateStruct');
555
        }
556
557
        if (!is_bool($locationCreateStruct->hidden)) {
558
            throw new InvalidArgumentValue('hidden', $locationCreateStruct->hidden, 'LocationCreateStruct');
559
        }
560
561
        if ($locationCreateStruct->remoteId !== null && (!is_string($locationCreateStruct->remoteId) || empty($locationCreateStruct->remoteId))) {
562
            throw new InvalidArgumentValue('remoteId', $locationCreateStruct->remoteId, 'LocationCreateStruct');
563
        }
564
565
        if ($locationCreateStruct->sortField !== null && !$this->isValidLocationSortField($locationCreateStruct->sortField)) {
566
            throw new InvalidArgumentValue('sortField', $locationCreateStruct->sortField, 'LocationCreateStruct');
567
        }
568
569
        if ($locationCreateStruct->sortOrder !== null && !$this->isValidLocationSortOrder($locationCreateStruct->sortOrder)) {
570
            throw new InvalidArgumentValue('sortOrder', $locationCreateStruct->sortOrder, 'LocationCreateStruct');
571
        }
572
573
        $remoteId = $locationCreateStruct->remoteId;
574
        if (null === $remoteId) {
575
            $remoteId = $this->getUniqueHash($locationCreateStruct);
576
        } else {
577
            try {
578
                $this->locationHandler->loadByRemoteId($remoteId);
579
                throw new InvalidArgumentException(
580
                    '$locationCreateStructs',
581
                    "Another Location with remoteId '{$remoteId}' exists"
582
                );
583
            } catch (NotFoundException $e) {
584
                // Do nothing
585
            }
586
        }
587
588
        return new SPILocationCreateStruct(
589
            array(
590
                'priority' => $locationCreateStruct->priority,
591
                'hidden' => $locationCreateStruct->hidden,
592
                // If we declare the new Location as hidden, it is automatically invisible
593
                // Otherwise it picks up visibility from parent Location
594
                // Note: There is no need to check for hidden status of parent, as hidden Location
595
                // is always invisible as well
596
                'invisible' => ($locationCreateStruct->hidden === true || $parentLocation->invisible),
597
                'remoteId' => $remoteId,
598
                'contentId' => $contentId,
599
                'contentVersion' => $contentVersionNo,
600
                // pathIdentificationString will be set in storage
601
                'pathIdentificationString' => null,
602
                'mainLocationId' => $mainLocation,
603
                'sortField' => $locationCreateStruct->sortField !== null ? $locationCreateStruct->sortField : Location::SORT_FIELD_NAME,
604
                'sortOrder' => $locationCreateStruct->sortOrder !== null ? $locationCreateStruct->sortOrder : Location::SORT_ORDER_ASC,
605
                'parentId' => $locationCreateStruct->parentLocationId,
606
            )
607
        );
608
    }
609
610
    /**
611
     * Checks if given $sortField value is one of the defined sort field constants.
612
     *
613
     * @param mixed $sortField
614
     *
615
     * @return bool
616
     */
617
    public function isValidLocationSortField($sortField)
618
    {
619
        switch ($sortField) {
620
            case APILocation::SORT_FIELD_PATH:
621
            case APILocation::SORT_FIELD_PUBLISHED:
622
            case APILocation::SORT_FIELD_MODIFIED:
623
            case APILocation::SORT_FIELD_SECTION:
624
            case APILocation::SORT_FIELD_DEPTH:
625
            case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
626
            case APILocation::SORT_FIELD_CLASS_NAME:
627
            case APILocation::SORT_FIELD_PRIORITY:
628
            case APILocation::SORT_FIELD_NAME:
629
            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...
630
            case APILocation::SORT_FIELD_NODE_ID:
631
            case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
632
                return true;
633
        }
634
635
        return false;
636
    }
637
638
    /**
639
     * Checks if given $sortOrder value is one of the defined sort order constants.
640
     *
641
     * @param mixed $sortOrder
642
     *
643
     * @return bool
644
     */
645
    public function isValidLocationSortOrder($sortOrder)
646
    {
647
        switch ($sortOrder) {
648
            case APILocation::SORT_ORDER_DESC:
649
            case APILocation::SORT_ORDER_ASC:
650
                return true;
651
        }
652
653
        return false;
654
    }
655
656
    /**
657
     * Checks if given $priority is valid.
658
     *
659
     * @param int $priority
660
     *
661
     * @return bool
662
     */
663
    public function isValidLocationPriority($priority)
664
    {
665
        if ($priority === null) {
666
            return true;
667
        }
668
669
        return is_int($priority) && $priority >= self::MIN_LOCATION_PRIORITY && $priority <= self::MAX_LOCATION_PRIORITY;
670
    }
671
672
    /**
673
     * Validates given translated list $list, which should be an array of strings with language codes as keys.
674
     *
675
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
676
     *
677
     * @param mixed $list
678
     * @param string $argumentName
679
     */
680
    public function validateTranslatedList($list, $argumentName)
681
    {
682
        if (!is_array($list)) {
683
            throw new InvalidArgumentType($argumentName, 'array', $list);
684
        }
685
686
        foreach ($list as $languageCode => $translation) {
687
            $this->contentLanguageHandler->loadByLanguageCode($languageCode);
688
689
            if (!is_string($translation)) {
690
                throw new InvalidArgumentType($argumentName . "['$languageCode']", 'string', $translation);
691
            }
692
        }
693
    }
694
695
    /**
696
     * Returns \DateTime object from given $timestamp in environment timezone.
697
     *
698
     * This method is needed because constructing \DateTime with $timestamp will
699
     * return the object in UTC timezone.
700
     *
701
     * @param int $timestamp
702
     *
703
     * @return \DateTime
704
     */
705
    public function getDateTime($timestamp)
706
    {
707
        $dateTime = new DateTime();
708
        $dateTime->setTimestamp($timestamp);
709
710
        return $dateTime;
711
    }
712
713
    /**
714
     * Creates unique hash string for given $object.
715
     *
716
     * Used for remoteId.
717
     *
718
     * @param object $object
719
     *
720
     * @return string
721
     */
722
    public function getUniqueHash($object)
723
    {
724
        return md5(uniqid(get_class($object), true));
725
    }
726
}
727