Test Failed
Push — master ( bb5d16...dcbbf0 )
by Bálint
03:37
created

ObjectModelSerializer::_writeFeedElements()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
c 1
b 0
f 0
dl 0
loc 27
rs 8.8333
cc 7
nc 11
nop 6
1
<?php
2
3
namespace POData\ObjectModel;
4
5
use ArrayAccess;
6
use DateTimeZone;
7
use POData\Common\InvalidOperationException;
8
use POData\Common\Messages;
9
use POData\Common\ODataException;
10
use POData\Common\ODataConstants;
11
use POData\IService;
12
use POData\Providers\Metadata\ResourceType;
13
use POData\Providers\Metadata\ResourceTypeKind;
14
use POData\Providers\Metadata\ResourcePropertyKind;
15
use POData\Providers\Metadata\ResourceProperty;
16
use POData\Providers\Metadata\Type\Binary;
17
use POData\Providers\Metadata\Type\Boolean;
18
use POData\Providers\Metadata\Type\StringType;
19
use POData\Providers\Metadata\Type\DateTime;
20
use POData\Providers\Metadata\Type\DateTimeTz;
21
use POData\Providers\Metadata\Type\Date;
22
use POData\Providers\Query\QueryType;
23
use ReflectionClass;
24
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
25
use POData\UriProcessor\RequestDescription;
26
use POData\Writers\Json\JsonLightMetadataLevel;
27
28
29
/**
30
 * Class ObjectModelSerializer
31
 * @package POData\ObjectModel
32
 */
33
class ObjectModelSerializer extends ObjectModelSerializerBase
34
{
35
    /**
36
     * Creates new instance of ObjectModelSerializer.
37
     *
38
     * @param IService $service
39
     *
40
     * @param RequestDescription $request the  request submitted by the client.
41
     *
42
     */
43
    public function __construct(IService $service, RequestDescription $request)
44
    {
45
        parent::__construct($service, $request);
46
    }
47
48
    /**
49
     * Write a top level entry resource.
50
     *
51
     * @param mixed $entryObject Reference to the entry object to be written.
52
     *
53
     * @return ODataEntry
54
     */
55
    public function writeTopLevelElement($entryObject)
56
    {
57
        $requestTargetSource = $this->request->getTargetSource();
58
59
        $resourceType = null;
60
        if ($requestTargetSource == TargetSource::ENTITY_SET) {
61
            $resourceType = $this->request->getTargetResourceType();
62
        } else {
63
            $this->assert(
64
                $requestTargetSource == TargetSource::PROPERTY,
65
                '$requestTargetSource == TargetSource::PROPERTY'
66
            );
67
            $resourceProperty = $this->request->getProjectedProperty();
68
            //$this->assert($resourceProperty->getKind() == ResourcePropertyKind::RESOURCE_REFERENCE, '$resourceProperty->getKind() == ResourcePropertyKind::RESOURCE_REFERENCE');
69
            $resourceType = $resourceProperty->getResourceType();
70
        }
71
72
        $needPop = $this->pushSegmentForRoot();
73
        $entry = $this->_writeEntryElement(
74
            $entryObject,
75
            $resourceType,
76
            $this->request->getRequestUrl()->getUrlAsString(),
77
            $this->request->getContainerName()
78
        );
79
80
        $toplevel_properties = $this->_writeCustomTopLevelProperties();
81
        array_push($entry->customProperties->properties, ...$toplevel_properties);
82
83
        $this->popSegment($needPop);
84
        return $entry;
85
    }
86
87
    /**
88
     * Write top level feed element.
89
     *
90
     * @param array &$entryObjects Array of entry resources to be written.
91
     *
92
     * @return ODataFeed.
0 ignored issues
show
Documentation Bug introduced by
The doc comment ODataFeed. at position 0 could not be parsed: Unknown type name 'ODataFeed.' at position 0 in ODataFeed..
Loading history...
93
     */
94
    public function writeTopLevelElements(&$entryObjects)
95
    {
96
        $this->assert(is_iterable($entryObjects), 'is_iterable($entryObjects)');
97
        $requestTargetSource = $this->request->getTargetSource();
98
        $title = null;
99
        if ($requestTargetSource == TargetSource::ENTITY_SET) {
100
            $title = $this->request->getContainerName();
101
        } else {
102
            $this->assert(
103
                $requestTargetSource == TargetSource::PROPERTY,
104
                '$requestTargetSource == TargetSource::PROPERTY'
105
            );
106
            $resourceProperty = $this->request->getProjectedProperty();
107
            $this->assert(
108
                $resourceProperty->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE,
109
                '$resourceProperty->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE'
110
            );
111
            $title = $resourceProperty->getName();
112
        }
113
114
        $relativeUri = $this->request->getIdentifier();
115
        $feed = new ODataFeed();
116
117
        if ($this->request->queryType == QueryType::ENTITIES_WITH_COUNT) {
118
            $feed->rowCount = $this->request->getCountValue();
119
        }
120
121
        $toplevel_properties = $this->_writeCustomTopLevelProperties();
122
        array_push($feed->customProperties->properties, ...$toplevel_properties);
123
124
        $needPop = $this->pushSegmentForRoot();
125
        $targetResourceType = $this->request->getTargetResourceType();
126
        $this->_writeFeedElements(
127
            $entryObjects,
128
            $targetResourceType,
129
            $title,
130
            $this->request->getRequestUrl()->getUrlAsString(),
131
            $relativeUri,
132
            $feed
133
        );
134
        $this->popSegment($needPop);
135
        return $feed;
136
    }
137
138
    /**
139
     * Write top level url element.
140
     *
141
     * @param mixed $entryObject The entry resource whose url to be written.
142
     *
143
     * @return ODataURL
144
     */
145
    public function writeUrlElement($entryObject)
146
    {
147
        $url = new ODataURL();
148
        if (!is_null($entryObject)) {
149
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
150
            $relativeUri = $this->getEntryInstanceKey(
151
                $entryObject,
152
                $currentResourceType,
153
                $this->getCurrentResourceSetWrapper()->getName()
154
            );
155
156
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
157
        }
158
159
        return $url;
160
    }
161
162
    /**
163
     * Write top level url collection.
164
     *
165
     * @param array $entryObjects Array of entry resources
166
     * whose url to be written.
167
     *
168
     * @return ODataURLCollection
169
     */
170
    public function writeUrlElements($entryObjects)
171
    {
172
        $urls = new ODataURLCollection();
173
        if (!empty($entryObjects)) {
174
            $i = 0;
175
            foreach ($entryObjects as $entryObject) {
176
                $urls->urls[$i] = $this->writeUrlElement($entryObject);
177
                $i++;
178
            }
179
180
            if ($i > 0 && $this->needNextPageLink(count($entryObjects))) {
181
                $urls->nextPageLink = $this->getNextLinkUri($entryObjects[$i - 1], $this->request->getRequestUrl()->getUrlAsString());
182
            }
183
        }
184
185
        if ($this->request->queryType == QueryType::ENTITIES_WITH_COUNT) {
186
            $urls->count = $this->request->getCountValue();
187
        }
188
189
        return $urls;
190
    }
191
192
    /**
193
     * Write top level complex resource.
194
     *
195
     * @param mixed                &$complexValue         The complex object to be
196
     *                                                    written.
197
     * @param string               $propertyName          The name of the
198
     *                                                    complex property.
199
     * @param ResourceType         &$resourceType         Describes the type of
200
     *                                                    complex object.
201
     *
202
     * @return ODataPropertyContent
203
     */
204
    public function writeTopLevelComplexObject(
205
        &$complexValue,
206
        $propertyName,
207
        ResourceType &$resourceType
208
    ) {
209
        $propertyContent = new ODataPropertyContent();
210
        $this->_writeComplexValue(
211
            $complexValue,
212
            $propertyName, $resourceType, null,
213
            $propertyContent
214
        );
215
216
        return $propertyContent;
217
    }
218
219
    /**
220
     * Write top level bag resource.
221
     *
222
     * @param mixed                &$BagValue             The bag object to be
223
     *                                                    written.
224
     * @param string               $propertyName          The name of the
225
     *                                                    bag property.
226
     * @param ResourceType         &$resourceType         Describes the type of
227
     *                                                    bag object.
228
     *
229
     * @return ODataPropertyContent
230
     */
231
    public function writeTopLevelBagObject(
232
        &$BagValue,
233
        $propertyName,
234
        ResourceType &$resourceType
235
    ) {
236
237
        $propertyContent = new ODataPropertyContent();
238
        $this->_writeBagValue(
239
            $BagValue,
240
            $propertyName, $resourceType, null,
241
            $propertyContent
242
        );
243
244
        return $propertyContent;
245
    }
246
247
    /**
248
     * Write top level primitive value.
249
     *
250
     * @param mixed                &$primitiveValue       The primitve value to be
251
     *                                                    written.
252
     * @param ResourceProperty     &$resourceProperty     Resource property
253
     *                                                    describing the
254
     *                                                    primitive property
255
     *                                                    to be written.
256
     *
257
     * @return ODataPropertyContent
258
     */
259
    public function writeTopLevelPrimitive(
260
        &$primitiveValue,
261
        ResourceProperty &$resourceProperty
262
    ) {
263
        $propertyContent = new ODataPropertyContent();
264
        $propertyContent->properties[] = new ODataProperty();
265
        $this->_writePrimitiveValue(
266
            $primitiveValue,
267
            $resourceProperty,
268
            $propertyContent->properties[0]
269
        );
270
271
        return $propertyContent;
272
    }
273
274
    /**
275
     * Write an entry element.
276
     *
277
     * @param mixed        $entryObject  Object representing entry element.
278
     * @param ResourceType $resourceType Expected type of the entry object.
279
     * @param string       $absoluteUri   Absolute uri of the entry element.
280
     * @param string       $relativeUri   Relative uri of the entry element.
281
     *
282
     * @return ODataEntry
283
     */
284
    private function _writeEntryElement(
285
        $entryObject,
286
        ResourceType $resourceType,
287
        $absoluteUri,
0 ignored issues
show
Unused Code introduced by
The parameter $absoluteUri is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

287
        /** @scrutinizer ignore-unused */ $absoluteUri,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
288
        $relativeUri
0 ignored issues
show
Unused Code introduced by
The parameter $relativeUri is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

288
        /** @scrutinizer ignore-unused */ $relativeUri

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
289
    ) {
290
        $entry = new ODataEntry();
291
        $entry->resourceSetName = $this->getCurrentResourceSetWrapper()->getName();
292
293
        if (is_null($entryObject)) {
294
            //According to atom standard an empty entry must have an Author
295
            //node.
296
        } else {
297
            $actualResourceType = $this->service->getProvidersWrapper()->resolveResourceTypeByClassname(get_class($entryObject));
298
            if ($actualResourceType) {
299
                $actualResourceSet = $actualResourceType->getCustomState();
300
            } else {
301
                $actualResourceType = $resourceType;
302
                $actualResourceSet = $this->getCurrentResourceSetWrapper();
303
            }
304
            $relativeUri = $this->getEntryInstanceKey(
305
                $entryObject,
306
                $actualResourceType,
307
                $actualResourceSet->getName()
308
            );
309
            $entry->resourceSetName = $actualResourceSet->getName();
310
311
            $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
312
            $title = $resourceType->getName();
313
            $this->_writeMediaResourceMetadata(
314
                $entryObject,
315
                $actualResourceType,
316
                $title,
317
                $relativeUri,
318
                $entry
319
            );
320
321
            $entry->id = $absoluteUri;
322
            $entry->eTag = $this->getETagForEntry($entryObject, $resourceType);
323
            $entry->title = $title;
324
            $entry->editLink = $relativeUri;
325
            $entry->type = $actualResourceType->getFullName();
326
327
            // Do not calculate the custom properties if the metadata level is none
328
            $responseContentType = $this->service->getResponseContentType($this->request, $this->request->getUriProcessor(), $this->service);
0 ignored issues
show
Bug introduced by
The method getResponseContentType() does not exist on POData\IService. Since it exists in all sub-types, consider adding an abstract or default implementation to POData\IService. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

328
            /** @scrutinizer ignore-call */ 
329
            $responseContentType = $this->service->getResponseContentType($this->request, $this->request->getUriProcessor(), $this->service);
Loading history...
329
            $matches = [];
330
            preg_match('/[^;]+(?<accept_ext>;[^;]+)*/', $responseContentType, $matches);
331
            $accept_ext = isset($matches['accept_ext']) ? $matches['accept_ext'] : '';
332
            $accept_extensions_tmp = explode(';', trim($accept_ext, ' \n\r\t\v\0;'));
333
            $accept_extensions = [];
334
            foreach($accept_extensions_tmp as $accept_extension) {
335
                $parts = explode('=', $accept_extension);
336
                $accept_extensions[$parts[0]] = $accept_extension;
337
            }
338
339
            if (!isset($accept_extensions['odata']) || $accept_extensions['odata'] != JsonLightMetadataLevel::NONE) {
340
                $this->_writeCustomProperties(
341
                    $entryObject,
342
                    $entry->customProperties
343
                );
344
            }
345
346
            $odataPropertyContent = new ODataPropertyContent();
347
            $this->_writeObjectProperties(
348
                $entryObject,
349
                $actualResourceType,
350
                $absoluteUri,
351
                $relativeUri,
352
                $entry,
353
                $odataPropertyContent
354
            );
355
            $entry->propertyContent = $odataPropertyContent;
356
        }
357
358
        return $entry;
359
    }
360
361
    /**
362
     * Writes the feed elements
363
     *
364
     * @param array        &$entryObjects Array of entries in the feed element.
365
     * @param ResourceType &$resourceType The resource type of the f the elements
366
     *                                    in the collection.
367
     * @param string       $title         Title of the feed element.
368
     * @param string       $absoluteUri   Absolute uri representing the feed element.
369
     * @param string       $relativeUri   Relative uri representing the feed element.
370
     * @param ODataFeed    &$feed    Feed to write to.
371
     *
372
     * @return void
373
     */
374
    private function _writeFeedElements(
375
        &$entryObjects,
376
        ResourceType &$resourceType,
377
        $title,
378
        $absoluteUri,
379
        $relativeUri,
380
        ODataFeed &$feed
381
    ) {
382
        $this->assert(is_iterable($entryObjects) || $entryObjects instanceof ArrayAccess, '_writeFeedElements::is_iterable($entryObjects)');
383
        $feed->id = $absoluteUri;
384
        $feed->title = $title;
385
        $feed->selfLink = new ODataLink();
386
        $feed->selfLink->name = ODataConstants::ATOM_SELF_RELATION_ATTRIBUTE_VALUE;
387
        $feed->selfLink->title = $title;
388
        $feed->selfLink->url = $relativeUri;
389
390
        if (empty($entryObjects)) {
391
            //TODO // ATOM specification: if a feed contains no entries,
392
            //then the feed should have at least one Author tag
393
        } else {
394
            foreach ($entryObjects as $entryObject) {
395
                $feed->entries[] = $this->_writeEntryElement($entryObject, $resourceType, null, null);
396
            }
397
398
            if ($this->needNextPageLink(count($entryObjects))) {
399
                $end = is_array($entryObjects) ? end($entryObjects) : (method_exists($entryObjects, 'last') ? $entryObjects->last() : null);
0 ignored issues
show
introduced by
The condition is_array($entryObjects) is always true.
Loading history...
400
                $feed->nextPageLink = $this->getNextLinkUri($end, $absoluteUri);
401
            }
402
        }
403
    }
404
405
    private function _writeCustomProperties(
406
        $customObject,
407
        ODataPropertyContent &$odataPropertyContent
408
    )
409
    {
410
        $properties = $this->service->getQueryProvider()->getCustomProperties($customObject);
0 ignored issues
show
Bug introduced by
The method getQueryProvider() does not exist on POData\IService. Since it exists in all sub-types, consider adding an abstract or default implementation to POData\IService. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

410
        $properties = $this->service->/** @scrutinizer ignore-call */ getQueryProvider()->getCustomProperties($customObject);
Loading history...
411
412
        //First write out primitve types
413
        foreach ($properties as $name => $value) {
414
            $odataProperty = new ODataProperty();
415
            $odataProperty->name = $name;
416
            $odataProperty->value = $value;
417
            $odataPropertyContent->properties[] = $odataProperty;
418
        }
419
    }
420
421
    private function _writeCustomTopLevelProperties()
422
    {
423
        $odataProperties = [];
424
        $properties = $this->service->getQueryProvider()->getCustomFeedProperties();
425
426
        //First write out primitve types
427
        foreach ($properties as $name => $value) {
428
            $odataProperty = new ODataProperty();
429
            $odataProperty->name = $name;
430
            $odataProperty->value = $value;
431
            $odataProperties[] = $odataProperty;
432
        }
433
        return $odataProperties;
434
    }
435
436
    /**
437
     * Write values of properties of given entry (resource) or complex object.
438
     *
439
     * @param mixed                $customObject          Entity or complex object
440
     *                                                    with properties
441
     *                                                    to write out.
442
     * @param ResourceType         &$resourceType         Resource type describing
443
     *                                                    the metadata of
444
     *                                                    the custom object.
445
     * @param string               $absoluteUri           Absolute uri for the given
446
     *                                                    entry object
447
     *                                                    NULL for complex object.
448
     * @param string               $relativeUri           Relative uri for the given
449
     *                                                    custom object.
450
     * @param ODataEntry           ODataEntry|null           ODataEntry instance to
451
     *                                                    place links and
452
     *                                                    expansion of the
453
     *                                                    entry object,
454
     *                                                    NULL for complex object.
455
     * @param ODataPropertyContent &$odataPropertyContent ODataPropertyContent
456
     *                                                    instance in which
457
     *                                                    to place the values.
458
     *
459
     * @return void
460
     */
461
    private function _writeObjectProperties(
462
        $customObject,
463
        ResourceType &$resourceType,
464
        $absoluteUri,
465
        $relativeUri,
466
        &$odataEntry,
467
        ODataPropertyContent &$odataPropertyContent
468
    ) {
469
        $resourceTypeKind = $resourceType->getResourceTypeKind();
470
        if (is_null($absoluteUri) == ($resourceTypeKind == ResourceTypeKind::ENTITY)
471
        ) {
472
            throw ODataException::createInternalServerError(
473
                Messages::badProviderInconsistentEntityOrComplexTypeUsage(
474
                    $resourceType->getName()
475
                )
476
            );
477
        }
478
479
        $this->assert(
480
            (($resourceTypeKind == ResourceTypeKind::ENTITY) && ($odataEntry instanceof ODataEntry))
481
            || (($resourceTypeKind == ResourceTypeKind::COMPLEX) && is_null($odataEntry)),
482
            '(($resourceTypeKind == ResourceTypeKind::ENTITY) && ($odataEntry instanceof ODataEntry))
483
            || (($resourceTypeKind == ResourceTypeKind::COMPLEX) && is_null($odataEntry))'
484
        );
485
        $projectionNodes = null;
486
        $navigationProperties = null;
487
        if ($resourceTypeKind == ResourceTypeKind::ENTITY) {
0 ignored issues
show
introduced by
The condition $resourceTypeKind == POD...esourceTypeKind::ENTITY is always false.
Loading history...
488
            $projectionNodes = $this->getProjectionNodes();
489
            $navigationProperties = array();
490
        }
491
492
        if (is_null($projectionNodes)) {
0 ignored issues
show
introduced by
The condition is_null($projectionNodes) is always true.
Loading history...
493
            //This is the code path to handle properties of Complex type
494
            //or Entry without projection (i.e. no expansion or selection)
495
            $resourceProperties = array();
496
            if ($resourceTypeKind == ResourceTypeKind::ENTITY) {
0 ignored issues
show
introduced by
The condition $resourceTypeKind == POD...esourceTypeKind::ENTITY is always false.
Loading history...
497
                // If custom object is an entry then it can contain navigation
498
                // properties which are invisible (because the corresponding
499
                // resource set is invisible).
500
                // IDSMP::getResourceProperties will give collection of properties
501
                // which are visible.
502
                $currentResourceSetWrapper1 = $this->getCurrentResourceSetWrapper();
503
                $resourceProperties = $this->service
504
                    ->getProvidersWrapper()
505
                    ->getResourceProperties(
506
                        $currentResourceSetWrapper1,
507
                        $resourceType
508
                    );
509
            } else {
510
                $resourceProperties = $resourceType->getAllProperties();
511
            }
512
513
            //First write out primitve types
514
            foreach ($resourceProperties as $name => $resourceProperty) {
515
                if ($resourceProperty->getKind() == ResourcePropertyKind::PRIMITIVE
516
                    || $resourceProperty->getKind() == (ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY)
517
                    || $resourceProperty->getKind() == (ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::ETAG)
518
                    || $resourceProperty->getKind() == (ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY | ResourcePropertyKind::ETAG)
519
                ) {
520
                    $odataProperty = new ODataProperty();
521
                    $primitiveValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
522
                    $this->_writePrimitiveValue($primitiveValue, $resourceProperty, $odataProperty);
523
                    $odataPropertyContent->properties[] = $odataProperty;
524
                }
525
            }
526
527
            //Write out bag and complex type
528
            $i = 0;
529
            foreach ($resourceProperties as $resourceProperty) {
530
                if ($resourceProperty->isKindOf(ResourcePropertyKind::BAG)) {
531
                    //Handle Bag Property (Bag of Primitive or complex)
532
                    $propertyValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
533
                    $resourceType2 = $resourceProperty->getResourceType();
534
                    $this->_writeBagValue(
535
                        $propertyValue,
536
                        $resourceProperty->getName(),
537
                        $resourceType2,
538
                        $relativeUri . '/' . $resourceProperty->getName(),
539
                        $odataPropertyContent
540
                    );
541
                } else {
542
                    $resourcePropertyKind = $resourceProperty->getKind();
543
                    if ($resourcePropertyKind == ResourcePropertyKind::COMPLEX_TYPE) {
544
                        $propertyValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
545
                        $resourceType1 = $resourceProperty->getResourceType();
546
                        $this->_writeComplexValue(
547
                            $propertyValue,
548
                            $resourceProperty->getName(),
549
                            $resourceType1,
550
                            $relativeUri . '/' . $resourceProperty->getName(),
551
                            $odataPropertyContent
552
                        );
553
                    } else if ($resourceProperty->getKind() == ResourcePropertyKind::PRIMITIVE
554
                        || $resourceProperty->getKind() == (ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY)
555
                        || $resourceProperty->getKind() == (ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::ETAG)
556
                        || $resourceProperty->getKind() == (ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY | ResourcePropertyKind::ETAG)
557
                    ) {
558
                        continue;
559
                    } else {
560
                            $this->assert(
561
                                ($resourcePropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE)
562
                             || ($resourcePropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE)
563
                             || ($resourcePropertyKind == ResourcePropertyKind::KEY_RESOURCE_REFERENCE),
564
                                '($resourcePropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE)
565
                             || ($resourcePropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE)
566
                             || ($resourcePropertyKind == ResourcePropertyKind::KEY_RESOURCE_REFERENCE)'
567
                            );
568
569
                        $navigationProperties[$i] = new NavigationPropertyInfo($resourceProperty, $this->shouldExpandSegment($resourceProperty->getName()));
570
                        if ($navigationProperties[$i]->expanded) {
571
                            $navigationProperties[$i]->value = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
572
                        }
573
574
                        $i++;
575
                    }
576
                }
577
            }
578
579
        } else { //This is the code path to handle projected properties of Entry
580
            $i = 0;
581
            foreach ($projectionNodes as $projectionNode) {
582
                $propertyName = $projectionNode->getPropertyName();
583
                $resourceProperty = $resourceType->resolveProperty($propertyName);
584
                $this->assert(!is_null($resourceProperty), '!is_null($resourceProperty)');
585
586
                if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY) {
587
                    $currentResourceSetWrapper2 = $this->getCurrentResourceSetWrapper();
588
                    $resourceProperties = $this->service
589
                        ->getProvidersWrapper()
590
                        ->getResourceProperties(
591
                            $currentResourceSetWrapper2,
592
                            $resourceType
593
                        );
594
                    //Check for the visibility of this navigation property
595
                    if (array_key_exists($resourceProperty->getName(), $resourceProperties)) {
596
                        $navigationProperties[$i] = new NavigationPropertyInfo($resourceProperty, $this->shouldExpandSegment($propertyName));
597
                        if ($navigationProperties[$i]->expanded) {
598
                            $navigationProperties[$i]->value = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
599
                        }
600
601
                        $i++;
602
                        continue;
603
                    }
604
                }
605
606
                //Primitve, complex or bag property
607
                $propertyValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
608
                $propertyTypeKind = $resourceProperty->getKind();
609
                $propertyResourceType = $resourceProperty->getResourceType();
610
                $this->assert(!is_null($propertyResourceType), '!is_null($propertyResourceType)');
611
                if (ResourceProperty::sIsKindOf($propertyTypeKind, ResourcePropertyKind::BAG)) {
612
                    $bagResourceType = $resourceProperty->getResourceType();
613
                    $this->_writeBagValue(
614
                        $propertyValue,
615
                        $propertyName,
616
                        $bagResourceType,
617
                        $relativeUri . '/' . $propertyName,
618
                        $odataPropertyContent
619
                    );
620
                } else if (ResourceProperty::sIsKindOf($propertyTypeKind, ResourcePropertyKind::PRIMITIVE)) {
621
                    $odataProperty = new ODataProperty();
622
                    $this->_writePrimitiveValue($propertyValue, $resourceProperty, $odataProperty);
623
                    $odataPropertyContent->properties[] = $odataProperty;
624
                } else if ($propertyTypeKind == ResourcePropertyKind::COMPLEX_TYPE) {
625
                    $complexResourceType = $resourceProperty->getResourceType();
626
                    $this->_writeComplexValue(
627
                        $propertyValue,
628
                        $propertyName,
629
                        $complexResourceType,
630
                        $relativeUri . '/' . $propertyName,
631
                        $odataPropertyContent
632
                    );
633
                } else {
634
                    //unexpected
635
                    $this->assert(false, '$propertyTypeKind = Primitive or Bag or ComplexType');
636
                }
637
            }
638
        }
639
640
        if (!is_null($navigationProperties)) {
0 ignored issues
show
introduced by
The condition is_null($navigationProperties) is always true.
Loading history...
641
            //Write out navigation properties (deferred or inline)
642
            foreach ($navigationProperties as $navigationPropertyInfo) {
643
                $propertyName = $navigationPropertyInfo->resourceProperty->getName();
644
                $type = $navigationPropertyInfo->resourceProperty->getKind() == ResourcePropertyKind::RESOURCE_REFERENCE ?
645
                    'application/atom+xml;type=entry' : 'application/atom+xml;type=feed';
646
                $link = new ODataLink();
647
                $link->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propertyName;
648
                $link->title = $propertyName;
649
                $link->type = $type;
650
                $link->url = $relativeUri . '/' . $propertyName;
651
652
                if ($navigationPropertyInfo->expanded) {
653
                    $propertyRelativeUri = $relativeUri . '/' . $propertyName;
654
                    $propertyAbsoluteUri = trim($absoluteUri, '/') . '/' . $propertyName;
655
                    $needPop = $this->pushSegmentForNavigationProperty($navigationPropertyInfo->resourceProperty);
656
                    $navigationPropertyKind = $navigationPropertyInfo->resourceProperty->getKind();
657
                    $this->assert(
658
                        $navigationPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE
659
                        || $navigationPropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE
660
                        || $navigationPropertyKind == ResourcePropertyKind::KEY_RESOURCE_REFERENCE,
661
                        '$navigationPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE
662
                        || $navigationPropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE
663
                        || $navigationPropertyKind == ResourcePropertyKind::KEY_RESOURCE_REFERENCE'
664
                    );
665
                    $currentResourceSetWrapper = $this->getCurrentResourceSetWrapper();
666
                    $this->assert(!is_null($currentResourceSetWrapper), '!is_null($currentResourceSetWrapper)');
667
                    $link->isExpanded = true;
668
                    if (!is_null($navigationPropertyInfo->value)) {
669
                        if ($navigationPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE) {
670
                            $inlineFeed = new ODataFeed();
671
                            $link->isCollection = true;
672
                            $currentResourceType = $currentResourceSetWrapper->getResourceType();
673
                            $this->_writeFeedElements(
674
                                $navigationPropertyInfo->value,
675
                                $currentResourceType,
676
                                $propertyName,
677
                                $propertyAbsoluteUri,
678
                                $propertyRelativeUri,
679
                                $inlineFeed
680
                            );
681
                            $link->expandedResult = $inlineFeed;
682
                        } else {
683
684
                            $link->isCollection = false;
685
                            $currentResourceType1 = $currentResourceSetWrapper->getResourceType();
686
687
                            $link->expandedResult = $this->_writeEntryElement(
688
                                $navigationPropertyInfo->value,
689
                                $currentResourceType1,
690
                                $propertyAbsoluteUri,
691
                                $propertyRelativeUri
692
                            );
693
                        }
694
                    } else {
695
                        $link->expandedResult = null;
696
                    }
697
698
                    $this->popSegment($needPop);
699
                }
700
701
                $odataEntry->links[] = $link;
702
            }
703
        }
704
    }
705
706
    /**
707
     * Writes a primitive value and related information to the given
708
     * ODataProperty instance.
709
     *
710
     * @param mixed            &$primitiveValue   The primitive value to write.
711
     * @param ResourceProperty &$resourceProperty The metadata of the primitive
712
     *                                            property value.
713
     * @param ODataProperty    &$odataProperty    ODataProperty instance to which
714
     *                                            the primitive value and related
715
     *                                            information to write out.
716
     *
717
     * @throws ODataException If given value is not primitive.
718
     *
719
     * @return void
720
     */
721
    private function _writePrimitiveValue(&$primitiveValue,
722
        ResourceProperty &$resourceProperty, ODataProperty &$odataProperty
723
    ) {
724
        if (is_object($primitiveValue)) {
725
            //TODO ERROR: The property 'PropertyName'
726
            //is defined as primitive type but value is an object
727
        }
728
729
730
        $odataProperty->name = $resourceProperty->getName();
731
        $instanceType = $resourceProperty->getInstanceType();
732
        if ($instanceType instanceof ReflectionClass) {
733
            $odataProperty->typeName = $instanceType->getName();
734
        } else {
735
            $odataProperty->typeName = $instanceType->getFullTypeName();
736
        }
737
        if (is_null($primitiveValue)) {
738
            $odataProperty->value = null;
739
        } else {
740
            $resourceType = $resourceProperty->getResourceType();
741
            $this->_primitiveToString(
742
                $resourceType,
743
                $primitiveValue,
744
                $odataProperty->value
0 ignored issues
show
Bug introduced by
It seems like $odataProperty->value can also be of type POData\ObjectModel\ODataBagContent and POData\ObjectModel\ODataPropertyContent; however, parameter $stringValue of POData\ObjectModel\Objec...r::_primitiveToString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

744
                /** @scrutinizer ignore-type */ $odataProperty->value
Loading history...
745
            );
746
        }
747
    }
748
749
    /**
750
     * Write value of a complex object.
751
     *
752
     * @param mixed                &$complexValue         Complex object to write.
753
     * @param string               $propertyName          Name of the
754
     *                                                    complex property
755
     *                                                    whose value need
756
     *                                                    to be written.
757
     * @param ResourceType         &$resourceType         Expected type
758
     *                                                    of the property.
759
     * @param string               $relativeUri           Relative uri for the
760
     *                                                    complex type element.
761
     * @param ODataPropertyContent &$odataPropertyContent Content to write to.
762
     *
763
     * @return void
764
     */
765
    private function _writeComplexValue(&$complexValue,
766
        $propertyName, ResourceType &$resourceType, $relativeUri,
767
        ODataPropertyContent &$odataPropertyContent
768
    ) {
769
        $odataProperty = new ODataProperty();
770
        $odataProperty->name = $propertyName;
771
        if (is_null($complexValue)) {
772
            $odataProperty->value = null;
773
            $odataProperty->typeName = $resourceType->getFullName();
774
        } else {
775
            $content = new ODataPropertyContent();
776
            $actualType = $this->_complexObjectToContent(
777
                $complexValue,
778
                $propertyName,
779
                $resourceType,
780
                $relativeUri,
781
                $content
782
            );
783
784
            $odataProperty->typeName = $actualType->getFullName();
785
            $odataProperty->value = $content;
786
        }
787
788
        $odataPropertyContent->properties[] = $odataProperty;
789
    }
790
791
    /**
792
     * Write value of a bag instance.
793
     *
794
     * @param array/NULL           &$BagValue             Bag value to write.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array/NULL at position 0 could not be parsed: Unknown type name 'array/NULL' at position 0 in array/NULL.
Loading history...
795
     * @param string               $propertyName          Property name of the bag.
796
     * @param ResourceType         &$resourceType         Type describing the
797
     *                                                    bag value.
798
     * @param string               $relativeUri           Relative Url to the bag.
799
     * @param ODataPropertyContent &$odataPropertyContent On return, this object
800
     *                                                    will hold bag value which
801
     *                                                    can be used by writers.
802
     *
803
     * @return void
804
     */
805
    private function _writeBagValue(&$BagValue,
806
        $propertyName, ResourceType &$resourceType, $relativeUri,
807
        ODataPropertyContent &$odataPropertyContent
808
    ) {
809
        $bagItemResourceTypeKind = $resourceType->getResourceTypeKind();
810
        $this->assert(
811
            $bagItemResourceTypeKind == ResourceTypeKind::PRIMITIVE
812
            || $bagItemResourceTypeKind == ResourceTypeKind::COMPLEX,
813
            '$bagItemResourceTypeKind == ResourceTypeKind::PRIMITIVE
814
            || $bagItemResourceTypeKind == ResourceTypeKind::COMPLEX'
815
        );
816
817
        $odataProperty = new ODataProperty();
818
        $odataProperty->name = $propertyName;
819
        $odataProperty->typeName = 'Collection(' . $resourceType->getFullName() . ')';
820
        if (is_null($BagValue) || (is_array($BagValue) && empty ($BagValue))) {
821
            $odataProperty->value = null;
822
        } else {
823
            $odataBagContent = new ODataBagContent();
824
            foreach ($BagValue as $itemValue) {
825
                if (!is_null($itemValue)) {
826
                    if ($bagItemResourceTypeKind == ResourceTypeKind::PRIMITIVE) {
827
                        $primitiveValueAsString = null;
828
                        $this->_primitiveToString($resourceType, $itemValue, $primitiveValueAsString);
829
                        $odataBagContent->propertyContents[] = $primitiveValueAsString;
830
                    } else if ($bagItemResourceTypeKind == ResourceTypeKind::COMPLEX) {
831
                        $complexContent = new ODataPropertyContent();
832
                        $actualType = $this->_complexObjectToContent(
0 ignored issues
show
Unused Code introduced by
The assignment to $actualType is dead and can be removed.
Loading history...
833
                            $itemValue,
834
                            $propertyName,
835
                            $resourceType,
836
                            $relativeUri,
837
                            $complexContent
838
                        );
839
                        //TODO add type in case of base type
840
                        $odataBagContent->propertyContents[] = $complexContent;
841
                    }
842
                }
843
            }
844
845
            $odataProperty->value = $odataBagContent;
846
        }
847
848
        $odataPropertyContent->properties[] = $odataProperty;
849
    }
850
851
    /**
852
     * Write media resource metadata (for MLE and Named Streams)
853
     *
854
     * @param mixed        $entryObject  The entry instance being serialized.
855
     * @param ResourceType &$resourceType Resource type of the entry instance.
856
     * @param string       $title         Title for the current
857
     *                                    current entry instance.
858
     * @param string       $relativeUri   Relative uri for the
859
     *                                    current entry instance.
860
     * @param ODataEntry   &$odataEntry   OData entry to write to.
861
     *
862
     * @return void
863
     */
864
    private function _writeMediaResourceMetadata(
865
        $entryObject,
866
        ResourceType &$resourceType,
867
        $title,
868
        $relativeUri,
869
        ODataEntry &$odataEntry
870
    ) {
871
        if ($resourceType->isMediaLinkEntry()) {
872
            $odataEntry->isMediaLinkEntry = true;
873
            $streamProvider = $this->service->getStreamProvider();
0 ignored issues
show
Bug introduced by
The method getStreamProvider() does not exist on POData\IService. Did you maybe mean getStreamProviderWrapper()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

873
            /** @scrutinizer ignore-call */ 
874
            $streamProvider = $this->service->getStreamProvider();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
874
            $eTag = $streamProvider->getStreamETag($entryObject, null);
875
            $readStreamUri = $streamProvider->getReadStreamUri($entryObject, null, $relativeUri);
876
            $mediaContentType = $streamProvider->getStreamContentType($entryObject, null);
877
            $mediaLink = new ODataMediaLink(
878
                $title,
879
                $streamProvider->getDefaultStreamEditMediaUri($relativeUri, null),
880
                $readStreamUri,
881
                $mediaContentType,
882
                $eTag
883
            );
884
885
            $odataEntry->mediaLink = $mediaLink;
886
        }
887
888
        if ($resourceType->hasNamedStream()) {
889
            foreach ($resourceType->getAllNamedStreams() as $title => $resourceStreamInfo) {
0 ignored issues
show
introduced by
$title is overwriting one of the parameters of this function.
Loading history...
890
                $eTag = $streamProvider->getStreamETag($entryObject, $resourceStreamInfo);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $streamProvider does not seem to be defined for all execution paths leading up to this point.
Loading history...
891
                $readStreamUri = $streamProvider->getReadStreamUri($entryObject, $resourceStreamInfo, $relativeUri);
892
                $mediaContentType = $streamProvider->getStreamContentType($entryObject, $resourceStreamInfo);
893
                $odataEntry->mediaLinks[] = new ODataMediaLink(
894
                    $title,
895
                    $streamProvider->getDefaultStreamEditMediaUri($relativeUri, $resourceStreamInfo),
896
                    $readStreamUri,
897
                    $mediaContentType,
898
                    $eTag
899
                );
900
            }
901
        }
902
    }
903
904
    /**
905
     * Convert the given primitive value to string.
906
     * Note: This method will not handle null primitive value.
907
     *
908
     * @param ResourceType &$primtiveResourceType Type of the primitive property
909
     *                                            whose value need to be converted.
910
     * @param mixed        $primitiveValue        Primitive value to convert.
911
     * @param string       &$stringValue          On return, this parameter will
912
     *                                            contain converted value.
913
     *
914
     * @return void
915
     */
916
    private function _primitiveToString(ResourceType &$primtiveResourceType,
917
        $primitiveValue, &$stringValue
918
    ) {
919
        $type = $primtiveResourceType->getInstanceType();
920
        if ($type instanceof Boolean) {
921
            $stringValue = ($primitiveValue === true) ? 'true' : 'false';
922
        } else if ($type instanceof Binary) {
923
            if (is_resource($primitiveValue)) {
924
                $stringValue = 'data:'.mime_content_type($primitiveValue).';base64,'.base64_encode(stream_get_contents($primitiveValue));
0 ignored issues
show
Bug introduced by
$primitiveValue of type resource is incompatible with the type string expected by parameter $filename of mime_content_type(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

924
                $stringValue = 'data:'.mime_content_type(/** @scrutinizer ignore-type */ $primitiveValue).';base64,'.base64_encode(stream_get_contents($primitiveValue));
Loading history...
925
            } else {
926
                $stringValue = base64_encode($primitiveValue);
927
            }
928
        /*} else if (($type instanceof DateTimeTz) && ($primitiveValue instanceof \DateTime || $primitiveValue instanceof \DateTimeImmutable)) {
929
            $stringValue = $primitiveValue->format(\DateTime::ATOM);*/
930
        } else if (($type instanceof Date) && ($primitiveValue instanceof \DateTime || $primitiveValue instanceof \DateTimeImmutable)) {
931
            $str_without_timezone = $primitiveValue->format('Y-m-d H:i:s');
932
            $stringValue = (new \DateTime($str_without_timezone, new DateTimeZone('UTC')))->format(\DateTime::ATOM);
933
        } else if (($type instanceof DateTime || $type instanceof StringType) && ($primitiveValue instanceof \DateTime || $primitiveValue instanceof \DateTimeImmutable)) {
934
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
935
        } else if ($type instanceof StringType && $primitiveValue instanceof \DateInterval) {
936
            $stringValue = (($primitiveValue->d*86400) + ($primitiveValue->h*3600) + ($primitiveValue->i*60) + $primitiveValue->s)*1000;
937
            // $stringValue = intval($primitiveValue->format('%s'))*1000; // Miliszekundumokkáé
938
        } else if ($type instanceof StringType) {
939
            $stringValue = mb_convert_encoding($primitiveValue, 'UTF-8');
940
        } else {
941
            $stringValue = strval($primitiveValue);
942
        }
943
    }
944
945
    /**
946
     * Write value of a complex object.
947
     * Note: This method will not handle null complex value.
948
     *
949
     * @param mixed                &$complexValue         Complex object to write.
950
     * @param string               $propertyName          Name of the
951
     *                                                    complex property
952
     *                                                    whose value
953
     *                                                    need to be written.
954
     * @param ResourceType         &$resourceType         Expected type of the
955
     *                                                    property.
956
     * @param string               $relativeUri           Relative uri for the
957
     *                                                    complex type element.
958
     * @param ODataPropertyContent &$odataPropertyContent Content to write to.
959
     *
960
     * @return ResourceType The actual type of the complex object.
961
     *
962
     * @return void
963
     */
964
    private function _complexObjectToContent(&$complexValue,
965
        $propertyName, ResourceType &$resourceType, $relativeUri,
966
        ODataPropertyContent &$odataPropertyContent
967
    ) {
968
        $count = count($this->complexTypeInstanceCollection);
969
        for ($i = 0; $i < $count; $i++) {
970
            if ($this->complexTypeInstanceCollection[$i] === $complexValue) {
971
                throw new InvalidOperationException(Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName));
972
            }
973
        }
974
975
        $this->complexTypeInstanceCollection[$count] = &$complexValue;
976
977
        //TODO function to resolve actual type from $resourceType
978
        $actualType = $resourceType;
979
        $odataEntry = null;
980
        $this->_writeObjectProperties(
981
            $complexValue, $actualType,
982
            null, $relativeUri, $odataEntry, $odataPropertyContent
983
        );
984
        unset($this->complexTypeInstanceCollection[$count]);
985
        return $actualType;
986
    }
987
}
988