Completed
Push — lazy_api_properties ( 349d84 )
by André
70:22 queued 50:11
created

DomainMapper::buildVersionInfoDomainObject()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 2
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
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\Core\Repository\Values\Content\ContentInfoProxy;
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 Meant for internal use by Repository.
43
 */
44
class DomainMapper
45
{
46
    const MAX_LOCATION_PRIORITY = 2147483647;
47
    const MIN_LOCATION_PRIORITY = -2147483648;
48
49
    /**
50
     * @var \eZ\Publish\SPI\Persistence\Content\Handler
51
     */
52
    protected $contentHandler;
53
54
    /**
55
     * @var \eZ\Publish\SPI\Persistence\Content\Location\Handler
56
     */
57
    protected $locationHandler;
58
59
    /**
60
     * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler
61
     */
62
    protected $contentTypeHandler;
63
64
    /**
65
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
66
     */
67
    protected $contentLanguageHandler;
68
69
    /**
70
     * @var FieldTypeRegistry
71
     */
72
    protected $fieldTypeRegistry;
73
74
    /**
75
     * Setups service with reference to repository.
76
     *
77
     * @param \eZ\Publish\SPI\Persistence\Content\Handler $contentHandler
78
     * @param \eZ\Publish\SPI\Persistence\Content\Location\Handler $locationHandler
79
     * @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler
80
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $contentLanguageHandler
81
     * @param FieldTypeRegistry $fieldTypeRegistry
82
     * @param ContentTypeDomainMapper $typeDomainMapper
83
     */
84
    public function __construct(
85
        ContentHandler $contentHandler,
86
        LocationHandler $locationHandler,
87
        TypeHandler $contentTypeHandler,
88
        LanguageHandler $contentLanguageHandler,
89
        FieldTypeRegistry $fieldTypeRegistry,
90
        ContentTypeDomainMapper $typeDomainMapper
91
    ) {
92
        $this->contentHandler = $contentHandler;
93
        $this->locationHandler = $locationHandler;
94
        $this->contentTypeHandler = $contentTypeHandler;
95
        $this->contentLanguageHandler = $contentLanguageHandler;
96
        $this->fieldTypeRegistry = $fieldTypeRegistry;
97
        $this->typeDomainMapper = $typeDomainMapper;
0 ignored issues
show
Bug introduced by
The property typeDomainMapper does not exist. Did you maybe forget to declare it?

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

class MyClass { }

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

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

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
98
    }
99
100
    /**
101
     * Builds a Content domain object from value object returned from persistence.
102
     *
103
     * @param \eZ\Publish\SPI\Persistence\Content $spiContent
104
     * @param ContentType|SPIType $contentType
105
     * @param array|null $fieldLanguages Language codes to filter fields on
106
     * @param string|null $fieldAlwaysAvailableLanguage Language code fallback if a given field is not found in $fieldLanguages
107
     *
108
     * @return \eZ\Publish\Core\Repository\Values\Content\Content
109
     */
110
    public function buildContentDomainObject(SPIContent $spiContent, ContentType $contentType = null, array $fieldLanguages = null, $fieldAlwaysAvailableLanguage = null)
111
    {
112
        $prioritizedFieldLanguageCode = null;
113
        $prioritizedLanguages = $fieldLanguages ?: [];
114
        if (!empty($prioritizedLanguages)) {
115
            $availableFieldLanguageMap = array_fill_keys($spiContent->versionInfo->languageCodes, true);
116
            foreach ($prioritizedLanguages as $prioritizedLanguage) {
117
                if (isset($availableFieldLanguageMap[$prioritizedLanguage])) {
118
                    $prioritizedFieldLanguageCode = $prioritizedLanguage;
119
                    break;
120
                }
121
            }
122
        }
123
124
        if (!$contentType instanceof ContentType) {
125
            $contentType = $this->typeDomainMapper->buildContentTypeProxyDomainObject(
126
                $spiContent->versionInfo->contentInfo->contentTypeId,
127
                $fieldLanguages ?: []
128
            );
129
        }
130
131
        return new Content(
132
            array(
133
                'internalFields' => $this->buildDomainFields($spiContent->fields, $contentType, $fieldLanguages, $fieldAlwaysAvailableLanguage),
134
                'versionInfo' => $this->mapVersionInfo(
135
                    $spiContent->versionInfo,
136
                    $this->mapContentInfo(
137
                        $spiContent->versionInfo->contentInfo,
138
                        $contentType
139
                    ),
140
                    $prioritizedLanguages
141
                ),
142
                'prioritizedFieldLanguageCode' => $prioritizedFieldLanguageCode,
143
            )
144
        );
145
    }
146
147
    /**
148
     * Returns an array of domain fields created from given array of SPI fields.
149
     *
150
     * @todo For ContentType loading in buildContentDomainObject to be able to be truly lazy loaded, this needs to be lazy to!
151
     *
152
     * @throws InvalidArgumentType On invalid $contentType
153
     *
154
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
155
     * @param ContentType $contentType
156
     * @param array $languages A language priority, filters returned fields and is used as prioritized language code on
157
     *                         returned value object. If not given all languages are returned.
158
     * @param string|null $alwaysAvailableLanguage Language code fallback if a given field is not found in $languages
159
     *
160
     * @return \eZ\Publish\API\Repository\Values\Content\Field[]
161
     */
162
    public function buildDomainFields(array $spiFields, ContentType $contentType, array $languages = null, $alwaysAvailableLanguage = null)
163
    {
164
        $fieldIdentifierMap = array();
165
        foreach ($contentType->fieldDefinitions as $fieldDefinitions) {
166
            $fieldIdentifierMap[$fieldDefinitions->id] = $fieldDefinitions->identifier;
167
        }
168
169
        $fieldInFilterLanguagesMap = array();
170
        if ($languages !== null && $alwaysAvailableLanguage !== null) {
171
            foreach ($spiFields as $spiField) {
172
                if (in_array($spiField->languageCode, $languages)) {
173
                    $fieldInFilterLanguagesMap[$spiField->fieldDefinitionId] = true;
174
                }
175
            }
176
        }
177
178
        $fields = array();
179
        foreach ($spiFields as $spiField) {
180
            // We ignore fields in content not part of the content type
181
            if (!isset($fieldIdentifierMap[$spiField->fieldDefinitionId])) {
182
                continue;
183
            }
184
185
            if ($languages !== null && !in_array($spiField->languageCode, $languages)) {
186
                // If filtering is enabled we ignore fields in other languages then $fieldLanguages, if:
187
                if ($alwaysAvailableLanguage === null) {
188
                    // Ignore field if we don't have $alwaysAvailableLanguageCode fallback
189
                    continue;
190
                } elseif (!empty($fieldInFilterLanguagesMap[$spiField->fieldDefinitionId])) {
191
                    // Ignore field if it exists in one of the filtered languages
192
                    continue;
193
                } elseif ($spiField->languageCode !== $alwaysAvailableLanguage) {
194
                    // Also ignore if field is not in $alwaysAvailableLanguageCode
195
                    continue;
196
                }
197
            }
198
199
            $fields[] = new Field(
200
                array(
201
                    'id' => $spiField->id,
202
                    'value' => $this->fieldTypeRegistry->getFieldType($spiField->type)
203
                        ->fromPersistenceValue($spiField->value),
204
                    'languageCode' => $spiField->languageCode,
205
                    'fieldDefIdentifier' => $fieldIdentifierMap[$spiField->fieldDefinitionId],
206
                    'fieldTypeIdentifier' => $spiField->type,
207
                )
208
            );
209
        }
210
211
        return $fields;
212
    }
213
214
    /**
215
     * Builds a VersionInfo domain object from value object returned from persistence.
216
     *
217
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $spiVersionInfo
218
     * @param array $prioritizedLanguages
219
     *
220
     * @return \eZ\Publish\Core\Repository\Values\Content\VersionInfo
221
     */
222
    public function buildVersionInfoDomainObject(SPIVersionInfo $spiVersionInfo, array $prioritizedLanguages = []) : APIVersionInfo
223
    {
224
        return $this->mapVersionInfo(
225
            $spiVersionInfo,
226
            $this->buildContentInfoDomainObject(
227
                $spiVersionInfo->contentInfo,
228
                null,
229
                $prioritizedLanguages
230
            ),
231
            $prioritizedLanguages
232
        );
233
    }
234
235
    private function mapVersionInfo(
236
        SPIVersionInfo $spiVersionInfo,
237
        ContentInfo $contentInfo,
238
        array $prioritizedLanguages = []
239
    ) : APIVersionInfo {
240
        // Map SPI statuses to API
241
        switch ($spiVersionInfo->status) {
242
            case SPIVersionInfo::STATUS_ARCHIVED:
243
                $status = APIVersionInfo::STATUS_ARCHIVED;
244
                break;
245
246
            case SPIVersionInfo::STATUS_PUBLISHED:
247
                $status = APIVersionInfo::STATUS_PUBLISHED;
248
                break;
249
250
            case SPIVersionInfo::STATUS_DRAFT:
251
            default:
252
                $status = APIVersionInfo::STATUS_DRAFT;
253
        }
254
255
        // Find prioritised language among names
256
        $prioritizedNameLanguageCode = null;
257
        foreach ($prioritizedLanguages as $prioritizedLanguage) {
258
            if (isset($spiVersionInfo->names[$prioritizedLanguage])) {
259
                $prioritizedNameLanguageCode = $prioritizedLanguage;
260
                break;
261
            }
262
        }
263
264
        return new VersionInfo(
265
            array(
266
                'id' => $spiVersionInfo->id,
267
                'versionNo' => $spiVersionInfo->versionNo,
268
                'modificationDate' => $this->getDateTime($spiVersionInfo->modificationDate),
269
                'creatorId' => $spiVersionInfo->creatorId,
270
                'creationDate' => $this->getDateTime($spiVersionInfo->creationDate),
271
                'status' => $status,
272
                'initialLanguageCode' => $spiVersionInfo->initialLanguageCode,
273
                'languageCodes' => $spiVersionInfo->languageCodes,
274
                'names' => $spiVersionInfo->names,
275
                'contentInfo' => $contentInfo,
276
                'prioritizedNameLanguageCode' => $prioritizedNameLanguageCode,
277
            )
278
        );
279
    }
280
281
    /**
282
     * Builds a ContentInfo domain object from value object returned from persistence.
283
     *
284
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo $spiContentInfo
285
     *
286
     * @return \eZ\Publish\API\Repository\Values\Content\ContentInfo
287
     */
288
    public function buildContentInfoDomainObject(SPIContentInfo $spiContentInfo, ContentType $contentType = null, array $prioritizedLanguages = [])
289
    {
290
        if (!$contentType instanceof ContentType) {
291
            $contentType = $this->typeDomainMapper->buildContentTypeProxyDomainObject(
292
                $spiContentInfo->contentTypeId,
293
                $prioritizedLanguages
294
            );
295
        }
296
297
        return $this->mapContentInfo($spiContentInfo, $contentType);
298
    }
299
300
    private function mapContentInfo(SPIContentInfo $spiContentInfo, ContentType $contentType) : ContentInfo
301
    {
302
        return new ContentInfo(
303
            [
304
                'id' => $spiContentInfo->id,
305
                'contentTypeId' => $spiContentInfo->contentTypeId,
306
                'contentType' => $contentType,
307
                'name' => $spiContentInfo->name,
308
                'sectionId' => $spiContentInfo->sectionId,
309
                'currentVersionNo' => $spiContentInfo->currentVersionNo,
310
                'published' => $spiContentInfo->isPublished,
311
                'ownerId' => $spiContentInfo->ownerId,
312
                'modificationDate' => $spiContentInfo->modificationDate == 0 ?
313
                    null :
314
                    $this->getDateTime($spiContentInfo->modificationDate),
315
                'publishedDate' => $spiContentInfo->publicationDate == 0 ?
316
                    null :
317
                    $this->getDateTime($spiContentInfo->publicationDate),
318
                'alwaysAvailable' => $spiContentInfo->alwaysAvailable,
319
                'remoteId' => $spiContentInfo->remoteId,
320
                'mainLanguageCode' => $spiContentInfo->mainLanguageCode,
321
                'mainLocationId' => $spiContentInfo->mainLocationId,
322
            ]
323
        );
324
    }
325
326
    public function buildContentInfoProxyList(array $ids, array $prioritizedLanguages = []) : array
327
    {
328
        $list = [];
329
        $generator = $this->generatorForContentInfoList($ids, $prioritizedLanguages);
330
        // @todo: once there is spi to load several content types we can also pre populate proxies for content types.
331
        foreach ($ids as $id) {
332
            $list[] = new ContentInfoProxy($generator, $id);
333
        }
334
335
        return $list;
336
    }
337
338
339
    private function generatorForContentInfoList(array $ids, array $prioritizedLanguages = []) : \Generator
340
    {
341
        $list = $this->contentHandler->loadContentInfoList($ids);
342
        while (!empty($list)) {
343
            $id = yield;
344
            yield $this->buildContentInfoDomainObject(
345
                $list[$id],
346
                null,
347
                $prioritizedLanguages
348
            );
349
            unset($list[$id]);
350
        }
351
    }
352
353
    /**
354
     * Builds API Relation object from provided SPI Relation object.
355
     *
356
     * @param \eZ\Publish\SPI\Persistence\Content\Relation $spiRelation
357
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $sourceContentInfo
358
     * @param \eZ\Publish\API\Repository\Values\Content\ContentInfo $destinationContentInfo
359
     *
360
     * @return \eZ\Publish\API\Repository\Values\Content\Relation
361
     */
362
    public function buildRelationDomainObject(
363
        SPIRelation $spiRelation,
364
        ContentInfo $sourceContentInfo,
365
        ContentInfo $destinationContentInfo
366
    ) {
367
        $sourceFieldDefinitionIdentifier = null;
368
        if ($spiRelation->sourceFieldDefinitionId !== null) {
369
            // todo: given content type group loading is lazy now we can simplify this to load using group of proxies for instance
370
            $contentType = $this->contentTypeHandler->load($sourceContentInfo->contentTypeId);
371
            foreach ($contentType->fieldDefinitions as $fieldDefinition) {
372
                if ($fieldDefinition->id !== $spiRelation->sourceFieldDefinitionId) {
373
                    continue;
374
                }
375
376
                $sourceFieldDefinitionIdentifier = $fieldDefinition->identifier;
377
                break;
378
            }
379
        }
380
381
        return new Relation(
382
            array(
383
                'id' => $spiRelation->id,
384
                'sourceFieldDefinitionIdentifier' => $sourceFieldDefinitionIdentifier,
385
                'type' => $spiRelation->type,
386
                'sourceContentInfo' => $sourceContentInfo,
387
                'destinationContentInfo' => $destinationContentInfo,
388
            )
389
        );
390
    }
391
392
    /**
393
     * Builds domain location object from provided persistence location.
394
     *
395
     * @param \eZ\Publish\SPI\Persistence\Content\Location $spiLocation
396
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo|null $spiContentInfo
397
     *
398
     * @return \eZ\Publish\API\Repository\Values\Content\Location
399
     */
400
    public function buildLocationDomainObject(SPILocation $spiLocation, SPIContentInfo $spiContentInfo = null)
401
    {
402
        // this is hardcoded workaround for (on purpose) missing ContentInfo on root location
403
        if ($spiLocation->id == 1) {
404
            $spiContentInfo = new SPIContentInfo(
405
                [
406
                    'id' => 0,
407
                    'name' => 'Top Level Nodes',
408
                    'sectionId' => 1,
409
                    'mainLocationId' => 1,
410
                    'contentTypeId' => 1,
411
                    'currentVersionNo' => 1,
412
                    'isPublished' => 1,
413
                    'ownerId' => 14, // admin user
414
                    'modificationDate' => 1030968000, //  first known 3.x commit
415
                    'publicationDate' => 1030968000,
416
                    'alwaysAvailable' => 1,
417
                    'remoteId' => null,
418
                    'mainLanguageCode' => 'eng-GB',
419
                ]
420
            );
421
        }
422
423
        // Get API ContentInfo object
424
        if ($spiContentInfo === null) {
425
            $contentInfo = new ContentInfoProxy(
426
                $this->generatorForContentInfoList([$spiLocation->contentId]),
427
                $spiLocation->contentId
428
            );
429
        } else {
430
            $contentInfo = $this->buildContentInfoDomainObject($spiContentInfo);
431
        }
432
433
        return $this->mapLocation($spiLocation, $contentInfo);
434
    }
435
436 View Code Duplication
    private function mapLocation(SPILocation $spiLocation, ContentInfo $contentInfo) : APILocation
437
    {
438
        return new Location(
439
            array(
440
                'contentInfo' => $contentInfo,
441
                'id' => $spiLocation->id,
442
                'priority' => $spiLocation->priority,
443
                'hidden' => $spiLocation->hidden,
444
                'invisible' => $spiLocation->invisible,
445
                'remoteId' => $spiLocation->remoteId,
446
                'parentLocationId' => $spiLocation->parentId,
447
                'pathString' => $spiLocation->pathString,
448
                'depth' => $spiLocation->depth,
449
                'sortField' => $spiLocation->sortField,
450
                'sortOrder' => $spiLocation->sortOrder,
451
            )
452
        );
453
    }
454
455
    /**
456
     * Build API Location and corresponding ContentInfo domain objects and apply to LocationSearchResult.
457
     *
458
     * Loading of ContentInfo objects are done in one operation.
459
     *
460
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $result SPI search result with SPI Location items as hits
461
     *
462
     * @return \eZ\Publish\SPI\Persistence\Content\Location[] Locations we did not find content info for is returned as an array.
463
     */
464
    public function buildLocationDomainObjectsOnSearchResult(SearchResult $result)
465
    {
466
        $contentIds = [];
467
        foreach ($result->searchHits as $hit) {
468
            $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...
469
        }
470
471
        $missingLocations = [];
472
        $contentInfoList = $this->contentHandler->loadContentInfoList($contentIds);
473
        foreach ($result->searchHits as $key => $hit) {
474
            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...
475
                $hit->valueObject = $this->buildLocationDomainObject(
476
                    $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...
477
                    $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...
478
                );
479
            } else {
480
                $missingLocations[] = $hit->valueObject;
481
                unset($result->searchHits[$key]);
482
                --$result->totalCount;
483
            }
484
        }
485
486
        return $missingLocations;
487
    }
488
489
    /**
490
     * Creates an array of SPI location create structs from given array of API location create structs.
491
     *
492
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
493
     *
494
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct $locationCreateStruct
495
     * @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
496
     * @param mixed $mainLocation
497
     * @param mixed $contentId
498
     * @param mixed $contentVersionNo
499
     *
500
     * @return \eZ\Publish\SPI\Persistence\Content\Location\CreateStruct
501
     */
502
    public function buildSPILocationCreateStruct(
503
        $locationCreateStruct,
504
        APILocation $parentLocation,
505
        $mainLocation,
506
        $contentId,
507
        $contentVersionNo
508
    ) {
509
        if (!$this->isValidLocationPriority($locationCreateStruct->priority)) {
510
            throw new InvalidArgumentValue('priority', $locationCreateStruct->priority, 'LocationCreateStruct');
511
        }
512
513
        if (!is_bool($locationCreateStruct->hidden)) {
514
            throw new InvalidArgumentValue('hidden', $locationCreateStruct->hidden, 'LocationCreateStruct');
515
        }
516
517
        if ($locationCreateStruct->remoteId !== null && (!is_string($locationCreateStruct->remoteId) || empty($locationCreateStruct->remoteId))) {
518
            throw new InvalidArgumentValue('remoteId', $locationCreateStruct->remoteId, 'LocationCreateStruct');
519
        }
520
521
        if ($locationCreateStruct->sortField !== null && !$this->isValidLocationSortField($locationCreateStruct->sortField)) {
522
            throw new InvalidArgumentValue('sortField', $locationCreateStruct->sortField, 'LocationCreateStruct');
523
        }
524
525
        if ($locationCreateStruct->sortOrder !== null && !$this->isValidLocationSortOrder($locationCreateStruct->sortOrder)) {
526
            throw new InvalidArgumentValue('sortOrder', $locationCreateStruct->sortOrder, 'LocationCreateStruct');
527
        }
528
529
        $remoteId = $locationCreateStruct->remoteId;
530
        if (null === $remoteId) {
531
            $remoteId = $this->getUniqueHash($locationCreateStruct);
0 ignored issues
show
Documentation introduced by
$locationCreateStruct is of type object<eZ\Publish\API\Re...t\LocationCreateStruct>, but the function expects a object<eZ\Publish\Core\Repository\Helper\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
532
        } else {
533
            try {
534
                $this->locationHandler->loadByRemoteId($remoteId);
535
                throw new InvalidArgumentException(
536
                    '$locationCreateStructs',
537
                    "Another Location with remoteId '{$remoteId}' exists"
538
                );
539
            } catch (NotFoundException $e) {
540
                // Do nothing
541
            }
542
        }
543
544
        return new SPILocationCreateStruct(
545
            array(
546
                'priority' => $locationCreateStruct->priority,
547
                'hidden' => $locationCreateStruct->hidden,
548
                // If we declare the new Location as hidden, it is automatically invisible
549
                // Otherwise it picks up visibility from parent Location
550
                // Note: There is no need to check for hidden status of parent, as hidden Location
551
                // is always invisible as well
552
                'invisible' => ($locationCreateStruct->hidden === true || $parentLocation->invisible),
553
                'remoteId' => $remoteId,
554
                'contentId' => $contentId,
555
                'contentVersion' => $contentVersionNo,
556
                // pathIdentificationString will be set in storage
557
                'pathIdentificationString' => null,
558
                'mainLocationId' => $mainLocation,
559
                'sortField' => $locationCreateStruct->sortField !== null ? $locationCreateStruct->sortField : Location::SORT_FIELD_NAME,
560
                'sortOrder' => $locationCreateStruct->sortOrder !== null ? $locationCreateStruct->sortOrder : Location::SORT_ORDER_ASC,
561
                'parentId' => $locationCreateStruct->parentLocationId,
562
            )
563
        );
564
    }
565
566
    /**
567
     * Checks if given $sortField value is one of the defined sort field constants.
568
     *
569
     * @param mixed $sortField
570
     *
571
     * @return bool
572
     */
573
    public function isValidLocationSortField($sortField)
574
    {
575
        switch ($sortField) {
576
            case APILocation::SORT_FIELD_PATH:
577
            case APILocation::SORT_FIELD_PUBLISHED:
578
            case APILocation::SORT_FIELD_MODIFIED:
579
            case APILocation::SORT_FIELD_SECTION:
580
            case APILocation::SORT_FIELD_DEPTH:
581
            case APILocation::SORT_FIELD_CLASS_IDENTIFIER:
582
            case APILocation::SORT_FIELD_CLASS_NAME:
583
            case APILocation::SORT_FIELD_PRIORITY:
584
            case APILocation::SORT_FIELD_NAME:
585
            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...
586
            case APILocation::SORT_FIELD_NODE_ID:
587
            case APILocation::SORT_FIELD_CONTENTOBJECT_ID:
588
                return true;
589
        }
590
591
        return false;
592
    }
593
594
    /**
595
     * Checks if given $sortOrder value is one of the defined sort order constants.
596
     *
597
     * @param mixed $sortOrder
598
     *
599
     * @return bool
600
     */
601
    public function isValidLocationSortOrder($sortOrder)
602
    {
603
        switch ($sortOrder) {
604
            case APILocation::SORT_ORDER_DESC:
605
            case APILocation::SORT_ORDER_ASC:
606
                return true;
607
        }
608
609
        return false;
610
    }
611
612
    /**
613
     * Checks if given $priority is valid.
614
     *
615
     * @param int $priority
616
     *
617
     * @return bool
618
     */
619
    public function isValidLocationPriority($priority)
620
    {
621
        if ($priority === null) {
622
            return true;
623
        }
624
625
        return is_int($priority) && $priority >= self::MIN_LOCATION_PRIORITY && $priority <= self::MAX_LOCATION_PRIORITY;
626
    }
627
628
    /**
629
     * Validates given translated list $list, which should be an array of strings with language codes as keys.
630
     *
631
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
632
     *
633
     * @param mixed $list
634
     * @param string $argumentName
635
     */
636
    public function validateTranslatedList($list, $argumentName)
637
    {
638
        if (!is_array($list)) {
639
            throw new InvalidArgumentType($argumentName, 'array', $list);
640
        }
641
642
        foreach ($list as $languageCode => $translation) {
643
            $this->contentLanguageHandler->loadByLanguageCode($languageCode);
644
645
            if (!is_string($translation)) {
646
                throw new InvalidArgumentType($argumentName . "['$languageCode']", 'string', $translation);
647
            }
648
        }
649
    }
650
651
    protected function getDateTime(int $timestamp) : DateTime
652
    {
653
        // Instead of using ctor we use setTimeStamp so timezone does not get set to UTC
654
        $dateTime = new DateTime();
655
        $dateTime->setTimestamp($timestamp);
656
657
        return $dateTime;
658
    }
659
660
    public function getUniqueHash(object $object) : string
661
    {
662
        return md5(uniqid(get_class($object), true));
663
    }
664
}
665