Passed
Branch master (f8ef50)
by Christopher
04:58
created

ObjectModelSerializer::isMatchPrimitive()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 3
eloc 6
nc 3
nop 1
1
<?php
2
3
namespace POData\ObjectModel;
4
5
use POData\Common\InvalidOperationException;
6
use POData\Common\Messages;
7
use POData\Common\ODataConstants;
8
use POData\Common\ODataException;
9
use POData\IService;
10
use POData\Providers\Metadata\ResourceProperty;
11
use POData\Providers\Metadata\ResourcePropertyKind;
12
use POData\Providers\Metadata\ResourceType;
13
use POData\Providers\Metadata\ResourceTypeKind;
14
use POData\Providers\Metadata\ResourceStreamInfo;
15
use POData\Providers\Metadata\Type\Binary;
16
use POData\Providers\Metadata\Type\Boolean;
17
use POData\Providers\Metadata\Type\DateTime;
18
use POData\Providers\Metadata\Type\StringType;
19
use POData\Providers\Query\QueryType;
20
use POData\UriProcessor\RequestDescription;
21
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
22
23
/**
24
 * Class ObjectModelSerializer.
25
 */
26
class ObjectModelSerializer extends ObjectModelSerializerBase implements IObjectSerialiser
27
{
28
    /**
29
     * Creates new instance of ObjectModelSerializer.
30
     *
31
     * @param IService           $service
32
     * @param RequestDescription $request the  request submitted by the client
33
     */
34
    public function __construct(IService $service, RequestDescription $request = null)
35
    {
36
        parent::__construct($service, $request);
37
    }
38
39
    /**
40
     * Write a top level entry resource.
41
     *
42
     * @param mixed $entryObject Reference to the entry object to be written
43
     *
44
     * @return ODataEntry
45
     */
46
    public function writeTopLevelElement($entryObject)
47
    {
48
        $requestTargetSource = $this->getRequest()->getTargetSource();
49
50 View Code Duplication
        if ($requestTargetSource == TargetSource::ENTITY_SET) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
51
            $resourceType = $this->getRequest()->getTargetResourceType();
52
        } else {
53
            assert($requestTargetSource == TargetSource::PROPERTY, '$requestTargetSource != TargetSource::PROPERTY');
54
            $resourceProperty = $this->getRequest()->getProjectedProperty();
55
            $resourceType = $resourceProperty->getResourceType();
56
        }
57
58
        $needPop = $this->pushSegmentForRoot();
59
        $entry = $this->_writeEntryElement(
60
            $entryObject,
61
            $resourceType,
62
            $this->getRequest()->getRequestUrl()->getUrlAsString(),
63
            $this->getRequest()->getContainerName()
64
        );
65
        $this->popSegment($needPop);
66
67
        return $entry;
68
    }
69
70
    /**
71
     * Write top level feed element.
72
     *
73
     * @param array &$entryObjects Array of entry resources to be written
74
     *
75
     * @return ODataFeed
76
     */
77
    public function writeTopLevelElements(&$entryObjects)
78
    {
79
        assert(is_array($entryObjects), '!is_array($entryObjects)');
80
        $requestTargetSource = $this->getRequest()->getTargetSource();
81
        if ($requestTargetSource == TargetSource::ENTITY_SET) {
82
            $title = $this->getRequest()->getContainerName();
83 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
84
            assert($requestTargetSource == TargetSource::PROPERTY, '$requestTargetSource != TargetSource::PROPERTY');
85
            $resourceProperty = $this->getRequest()->getProjectedProperty();
86
            assert(
87
                $resourceProperty->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE,
88
                '$resourceProperty->getKind() != ResourcePropertyKind::RESOURCESET_REFERENCE'
89
            );
90
            $title = $resourceProperty->getName();
91
        }
92
93
        $relativeUri = $this->getRequest()->getIdentifier();
94
        $feed = new ODataFeed();
95
96
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
97
            $feed->rowCount = $this->getRequest()->getCountValue();
98
        }
99
100
        $needPop = $this->pushSegmentForRoot();
101
        $targetResourceType = $this->getRequest()->getTargetResourceType();
102
        assert(null != $targetResourceType, 'Target resource type must not be null');
103
        $this->_writeFeedElements(
104
            $entryObjects,
105
            $targetResourceType,
106
            $title,
107
            $this->getRequest()->getRequestUrl()->getUrlAsString(),
108
            $relativeUri,
109
            $feed
110
        );
111
        $this->popSegment($needPop);
112
113
        return $feed;
114
    }
115
116
    /**
117
     * Write top level url element.
118
     *
119
     * @param mixed $entryObject The entry resource whose url to be written
120
     *
121
     * @return ODataURL
122
     */
123
    public function writeUrlElement($entryObject)
124
    {
125
        $url = new ODataURL();
126
        if (!is_null($entryObject)) {
127
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
128
            $relativeUri = $this->getEntryInstanceKey(
129
                $entryObject,
130
                $currentResourceType,
131
                $this->getCurrentResourceSetWrapper()->getName()
132
            );
133
134
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
135
        }
136
137
        return $url;
138
    }
139
140
    /**
141
     * Write top level url collection.
142
     *
143
     * @param array $entryObjects Array of entry resources
144
     *                            whose url to be written
145
     *
146
     * @return ODataURLCollection
147
     */
148
    public function writeUrlElements($entryObjects)
149
    {
150
        $urls = new ODataURLCollection();
151
        if (!empty($entryObjects)) {
152
            $i = 0;
153
            foreach ($entryObjects as $entryObject) {
154
                $urls->urls[$i] = $this->writeUrlElement($entryObject);
155
                ++$i;
156
            }
157
158
            if ($i > 0 && $this->needNextPageLink(count($entryObjects))) {
159
                $urls->nextPageLink = $this->getNextLinkUri(
160
                    $entryObjects[$i - 1],
161
                    $this->getRequest()->getRequestUrl()->getUrlAsString()
162
                );
163
            }
164
        }
165
166
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
167
            $urls->count = $this->getRequest()->getCountValue();
168
        }
169
170
        return $urls;
171
    }
172
173
    /**
174
     * Write top level complex resource.
175
     *
176
     * @param mixed        &$complexValue The complex object to be
177
     *                                    written
178
     * @param string       $propertyName  The name of the
179
     *                                    complex property
180
     * @param ResourceType &$resourceType Describes the type of
181
     *                                    complex object
182
     *
183
     * @return ODataPropertyContent
184
     */
185
    public function writeTopLevelComplexObject(
186
        &$complexValue,
187
        $propertyName,
188
        ResourceType & $resourceType
189
    ) {
190
        $propertyContent = new ODataPropertyContent();
191
        $this->_writeComplexValue(
192
            $complexValue,
193
            $propertyName,
194
            $resourceType,
195
            null,
196
            $propertyContent
197
        );
198
199
        return $propertyContent;
200
    }
201
202
    /**
203
     * Write top level bag resource.
204
     *
205
     * @param mixed        &$BagValue     The bag object to be
206
     *                                    written
207
     * @param string       $propertyName  The name of the
208
     *                                    bag property
209
     * @param ResourceType &$resourceType Describes the type of
210
     *                                    bag object
211
     *
212
     * @return ODataPropertyContent
213
     */
214
    public function writeTopLevelBagObject(
215
        &$BagValue,
216
        $propertyName,
217
        ResourceType & $resourceType
218
    ) {
219
        $propertyContent = new ODataPropertyContent();
220
        $this->_writeBagValue(
221
            $BagValue,
222
            $propertyName,
223
            $resourceType,
224
            null,
225
            $propertyContent
226
        );
227
228
        return $propertyContent;
229
    }
230
231
    /**
232
     * Write top level primitive value.
233
     *
234
     * @param mixed            &$primitiveValue   The primitve value to be
235
     *                                            written
236
     * @param ResourceProperty &$resourceProperty Resource property
237
     *                                            describing the
238
     *                                            primitive property
239
     *                                            to be written
240
     *
241
     * @return ODataPropertyContent
242
     */
243
    public function writeTopLevelPrimitive(
244
        &$primitiveValue,
245
        ResourceProperty & $resourceProperty
246
    ) {
247
        $propertyContent = new ODataPropertyContent();
248
        $propertyContent->properties[] = new ODataProperty();
249
        $this->_writePrimitiveValue(
250
            $primitiveValue,
251
            $resourceProperty,
252
            $propertyContent->properties[0]
253
        );
254
255
        return $propertyContent;
256
    }
257
258
    /**
259
     * Write an entry element.
260
     *
261
     * @param mixed        $entryObject  Object representing entry element
262
     * @param ResourceType $resourceType Expected type of the entry object
263
     * @param string       $absoluteUri  Absolute uri of the entry element
264
     * @param string       $relativeUri  Relative uri of the entry element
265
     *
266
     * @return ODataEntry
267
     */
268
    private function _writeEntryElement(
269
        $entryObject,
270
        ResourceType $resourceType,
271
        $absoluteUri,
0 ignored issues
show
Unused Code introduced by
The parameter $absoluteUri is not used and could be removed.

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

Loading history...
272
        $relativeUri
0 ignored issues
show
Unused Code introduced by
The parameter $relativeUri is not used and could be removed.

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

Loading history...
273
    ) {
274
        $entry = new ODataEntry();
275
        $entry->resourceSetName = $this->getCurrentResourceSetWrapper()->getName();
276
277
        if (is_null($entryObject)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
278
            //According to atom standard an empty entry must have an Author
279
            //node.
280
        } else {
281
            $relativeUri = $this->getEntryInstanceKey(
282
                $entryObject,
283
                $resourceType,
284
                $this->getCurrentResourceSetWrapper()->getName()
285
            );
286
287
            $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
288
            $title = $resourceType->getName();
289
            //TODO Resolve actual resource type
290
            $actualResourceType = $resourceType;
291
            $this->_writeMediaResourceMetadata(
292
                $entryObject,
293
                $actualResourceType,
294
                $title,
295
                $relativeUri,
296
                $entry
297
            );
298
299
            $entry->id = $absoluteUri;
300
            $entry->eTag = $this->getETagForEntry($entryObject, $resourceType);
301
            $entry->title = $title;
302
            $entry->editLink = $relativeUri;
303
            $entry->type = $actualResourceType->getFullName();
304
            $odataPropertyContent = new ODataPropertyContent();
305
            $this->_writeObjectProperties(
306
                $entryObject,
307
                $actualResourceType,
308
                $absoluteUri,
309
                $relativeUri,
310
                $entry,
311
                $odataPropertyContent
312
            );
313
            $entry->propertyContent = $odataPropertyContent;
314
        }
315
316
        return $entry;
317
    }
318
319
    /**
320
     * Writes the feed elements.
321
     *
322
     * @param array        &$entryObjects Array of entries in the feed element
323
     * @param ResourceType &$resourceType The resource type of the f the elements
324
     *                                    in the collection
325
     * @param string       $title         Title of the feed element
326
     * @param string       $absoluteUri   Absolute uri representing the feed element
327
     * @param string       $relativeUri   Relative uri representing the feed element
328
     * @param ODataFeed    &$feed         Feed to write to
329
     */
330
    private function _writeFeedElements(
331
        &$entryObjects,
332
        ResourceType & $resourceType,
333
        $title,
334
        $absoluteUri,
335
        $relativeUri,
336
        ODataFeed & $feed
337
    ) {
338
        assert(is_array($entryObjects), '!_writeFeedElements::is_array($entryObjects)');
339
        $feed->id = $absoluteUri;
340
        $feed->title = $title;
341
        $feed->selfLink = new ODataLink();
342
        $feed->selfLink->name = ODataConstants::ATOM_SELF_RELATION_ATTRIBUTE_VALUE;
343
        $feed->selfLink->title = $title;
344
        $feed->selfLink->url = $relativeUri;
345
346
        if (empty($entryObjects)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
347
            //TODO // ATOM specification: if a feed contains no entries,
348
            //then the feed should have at least one Author tag
349
        } else {
350
            foreach ($entryObjects as $entryObject) {
351
                $feed->entries[] = $this->_writeEntryElement($entryObject, $resourceType, null, null);
352
            }
353
354
            if ($this->needNextPageLink(count($entryObjects))) {
355
                $lastObject = end($entryObjects);
356
                $feed->nextPageLink = $this->getNextLinkUri($lastObject, $absoluteUri);
357
            }
358
        }
359
    }
360
361
    /**
362
     * Write values of properties of given entry (resource) or complex object.
363
     *
364
     * @param mixed        $customObject  Entity or complex object
365
     *                                    with properties
366
     *                                    to write out
367
     * @param ResourceType &$resourceType Resource type describing
368
     *                                    the metadata of
369
     *                                    the custom object
370
     * @param string       $absoluteUri   Absolute uri for the given
371
     *                                    entry object
372
     *                                    NULL for complex object
373
     * @param string       $relativeUri   Relative uri for the given
374
     *                                    custom object
375
     * @param ODataEntry           ODataEntry|null           ODataEntry instance to
376
     *                                                    place links and
377
     *                                                    expansion of the
378
     *                                                    entry object,
379
     *                                                    NULL for complex object
380
     * @param ODataPropertyContent &$odataPropertyContent ODataPropertyContent
381
     *                                                    instance in which
382
     *                                                    to place the values
383
     */
384
    private function _writeObjectProperties(
385
        $customObject,
386
        ResourceType & $resourceType,
387
        $absoluteUri,
388
        $relativeUri,
389
        &$odataEntry,
390
        ODataPropertyContent & $odataPropertyContent
391
    ) {
392
        $resourceTypeKind = $resourceType->getResourceTypeKind();
393
        if (is_null($absoluteUri) == ($resourceTypeKind == ResourceTypeKind::ENTITY)
394
        ) {
395
            throw ODataException::createInternalServerError(
396
                Messages::badProviderInconsistentEntityOrComplexTypeUsage(
397
                    $resourceType->getName()
398
                )
399
            );
400
        }
401
402
        assert(
403
            (($resourceTypeKind == ResourceTypeKind::ENTITY) && ($odataEntry instanceof ODataEntry))
404
            || (($resourceTypeKind == ResourceTypeKind::COMPLEX) && is_null($odataEntry)),
405
            '!(($resourceTypeKind == ResourceTypeKind::ENTITY) && ($odataEntry instanceof ODataEntry))'
406
            .' && !(($resourceTypeKind == ResourceTypeKind::COMPLEX) && is_null($odataEntry))'
407
        );
408
        $projectionNodes = null;
409
        $navigationProperties = null;
410
        if ($resourceTypeKind == ResourceTypeKind::ENTITY) {
411
            $projectionNodes = $this->getProjectionNodes();
412
            $navigationProperties = [];
413
        }
414
415
        if (is_null($projectionNodes)) {
416
            list($odataPropertyContent, $navigationProperties) = $this->writeObjectPropertiesUnexpanded(
417
                $customObject,
418
                $resourceType,
419
                $relativeUri,
420
                $odataPropertyContent,
421
                $resourceTypeKind,
422
                $navigationProperties
423
            );
424
        } else { //This is the code path to handle projected properties of Entry
425
            list($navigationProperties, $odataPropertyContent) = $this->writeObjectPropertiesExpanded(
426
                $customObject,
427
                $resourceType,
428
                $relativeUri,
429
                $odataPropertyContent,
430
                $projectionNodes,
431
                $navigationProperties
432
            );
433
        }
434
435
        if (!is_null($navigationProperties)) {
436
            //Write out navigation properties (deferred or inline)
437
            foreach ($navigationProperties as $navigationPropertyInfo) {
438
                $propertyName = $navigationPropertyInfo->resourceProperty->getName();
439
                $type = $navigationPropertyInfo->resourceProperty->getKind() == ResourcePropertyKind::RESOURCE_REFERENCE ?
440
                    'application/atom+xml;type=entry' : 'application/atom+xml;type=feed';
441
                $link = new ODataLink();
442
                $link->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propertyName;
443
                $link->title = $propertyName;
444
                $link->type = $type;
445
                $link->url = $relativeUri . '/' . $propertyName;
446
447
                if ($navigationPropertyInfo->expanded) {
448
                    $propertyRelativeUri = $relativeUri . '/' . $propertyName;
449
                    $propertyAbsoluteUri = trim($absoluteUri, '/') . '/' . $propertyName;
450
                    $needPop = $this->pushSegmentForNavigationProperty($navigationPropertyInfo->resourceProperty);
451
                    $navigationPropertyKind = $navigationPropertyInfo->resourceProperty->getKind();
452
                    assert(
453
                        $navigationPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE
454
                        || $navigationPropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE,
455
                        '$navigationPropertyKind != ResourcePropertyKind::RESOURCESET_REFERENCE 
456
                        && $navigationPropertyKind != ResourcePropertyKind::RESOURCE_REFERENCE'
457
                    );
458
                    $currentResourceSetWrapper = $this->getCurrentResourceSetWrapper();
459
                    assert(!is_null($currentResourceSetWrapper), 'is_null($currentResourceSetWrapper)');
460
                    $link->isExpanded = true;
461
                    if (!is_null($navigationPropertyInfo->value)) {
462
                        $currentResourceType = $currentResourceSetWrapper->getResourceType();
463
                        if ($navigationPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE) {
464
                            $inlineFeed = new ODataFeed();
465
                            $link->isCollection = true;
466
467
                            $this->_writeFeedElements(
468
                                $navigationPropertyInfo->value,
469
                                $currentResourceType,
470
                                $propertyName,
471
                                $propertyAbsoluteUri,
472
                                $propertyRelativeUri,
473
                                $inlineFeed
474
                            );
475
                            $link->expandedResult = $inlineFeed;
476
                        } else {
477
                            $link->isCollection = false;
478
                            $link->expandedResult = $this->_writeEntryElement(
479
                                $navigationPropertyInfo->value,
480
                                $currentResourceType,
481
                                $propertyAbsoluteUri,
482
                                $propertyRelativeUri
483
                            );
484
                        }
485
                    } else {
486
                        $link->expandedResult = null;
487
                    }
488
489
                    $this->popSegment($needPop);
490
                }
491
492
                $odataEntry->links[] = $link;
493
            }
494
        }
495
    }
496
497
    /**
498
     * Writes a primitive value and related information to the given
499
     * ODataProperty instance.
500
     *
501
     * @param mixed            &$primitiveValue   The primitive value to write
502
     * @param ResourceProperty &$resourceProperty The metadata of the primitive
503
     *                                            property value
504
     * @param ODataProperty    &$odataProperty    ODataProperty instance to which
505
     *                                            the primitive value and related
506
     *                                            information to write out
507
     *
508
     * @throws ODataException If given value is not primitive
509
     */
510
    private function _writePrimitiveValue(
511
        &$primitiveValue,
512
        ResourceProperty & $resourceProperty,
513
        ODataProperty & $odataProperty
514
    ) {
515
        if (is_object($primitiveValue)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
516
            //TODO ERROR: The property 'PropertyName'
517
            //is defined as primitive type but value is an object
518
        }
519
520
        $odataProperty->name = $resourceProperty->getName();
521
        $odataProperty->typeName = $resourceProperty->getInstanceType()->getFullTypeName();
0 ignored issues
show
Bug introduced by
The method getFullTypeName does only exist in POData\Providers\Metadata\Type\IType, but not in ReflectionClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
522
        if (is_null($primitiveValue)) {
523
            $odataProperty->value = null;
524
        } else {
525
            $resourceType = $resourceProperty->getResourceType();
526
            $odataProperty->value = $this->_primitiveToString($resourceType, $primitiveValue);
527
        }
528
    }
529
530
    /**
531
     * Write value of a complex object.
532
     *
533
     * @param mixed                &$complexValue         Complex object to write
534
     * @param string               $propertyName          Name of the
535
     *                                                    complex property
536
     *                                                    whose value need
537
     *                                                    to be written
538
     * @param ResourceType         &$resourceType         Expected type
539
     *                                                    of the property
540
     * @param string               $relativeUri           Relative uri for the
541
     *                                                    complex type element
542
     * @param ODataPropertyContent &$odataPropertyContent Content to write to
543
     */
544
    private function _writeComplexValue(
545
        &$complexValue,
546
        $propertyName,
547
        ResourceType & $resourceType,
548
        $relativeUri,
549
        ODataPropertyContent & $odataPropertyContent
550
    ) {
551
        $odataProperty = new ODataProperty();
552
        $odataProperty->name = $propertyName;
553
        if (is_null($complexValue)) {
554
            $odataProperty->value = null;
555
            $odataProperty->typeName = $resourceType->getFullName();
556
        } else {
557
            $content = new ODataPropertyContent();
558
            $actualType = $this->_complexObjectToContent(
559
                $complexValue,
560
                $propertyName,
561
                $resourceType,
562
                $relativeUri,
563
                $content
564
            );
565
566
            $odataProperty->typeName = $actualType->getFullName();
567
            $odataProperty->value = $content;
568
        }
569
570
        $odataPropertyContent->properties[] = $odataProperty;
571
    }
572
573
    /**
574
     * Write value of a bag instance.
575
     *
576
     * @param array/NULL           &$BagValue             Bag value to write
577
     * @param string               $propertyName          Property name of the bag
578
     * @param ResourceType         &$resourceType         Type describing the
579
     *                                                    bag value
580
     * @param string               $relativeUri           Relative Url to the bag
581
     * @param ODataPropertyContent &$odataPropertyContent On return, this object
582
     *                                                    will hold bag value which
583
     *                                                    can be used by writers
584
     */
585
    private function _writeBagValue(
586
        &$BagValue,
587
        $propertyName,
588
        ResourceType & $resourceType,
589
        $relativeUri,
590
        ODataPropertyContent & $odataPropertyContent
591
    ) {
592
        assert(null == $BagValue || is_array($BagValue), 'Bag parameter must be null or array');
593
        $bagItemResourceTypeKind = $resourceType->getResourceTypeKind();
594
        assert(
595
            $bagItemResourceTypeKind == ResourceTypeKind::PRIMITIVE
596
            || $bagItemResourceTypeKind == ResourceTypeKind::COMPLEX,
597
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
598
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
599
        );
600
601
        $odataProperty = new ODataProperty();
602
        $odataProperty->name = $propertyName;
603
        $odataProperty->typeName = 'Collection(' . $resourceType->getFullName() . ')';
604
605
        if (is_null($BagValue) || (is_array($BagValue) && empty($BagValue))) {
606
            $odataProperty->value = null;
607
        } else {
608
            $odataBagContent = new ODataBagContent();
609
            // strip out null elements
610
            $BagValue = array_diff($BagValue, [null]);
611
            foreach ($BagValue as $itemValue) {
612
                if ($bagItemResourceTypeKind == ResourceTypeKind::PRIMITIVE) {
613
                    $odataBagContent->propertyContents[] = $this->_primitiveToString($resourceType, $itemValue);
614
                } elseif ($bagItemResourceTypeKind == ResourceTypeKind::COMPLEX) {
615
                    $complexContent = new ODataPropertyContent();
616
                    $this->_complexObjectToContent(
617
                        $itemValue,
618
                        $propertyName,
619
                        $resourceType,
620
                        $relativeUri,
621
                        $complexContent
622
                    );
623
                    //TODO add type in case of base type
624
                    $odataBagContent->propertyContents[] = $complexContent;
625
                }
626
            }
627
628
            $odataProperty->value = $odataBagContent;
629
        }
630
631
        $odataPropertyContent->properties[] = $odataProperty;
632
    }
633
634
    /**
635
     * Write media resource metadata (for MLE and Named Streams).
636
     *
637
     * @param mixed        $entryObject   The entry instance being serialized
638
     * @param ResourceType &$resourceType Resource type of the entry instance
639
     * @param string       $title         Title for the current
640
     *                                    current entry instance
641
     * @param string       $relativeUri   Relative uri for the
642
     *                                    current entry instance
643
     * @param ODataEntry   &$odataEntry   OData entry to write to
644
     */
645
    private function _writeMediaResourceMetadata(
646
        $entryObject,
647
        ResourceType & $resourceType,
648
        $title,
649
        $relativeUri,
650
        ODataEntry & $odataEntry
651
    ) {
652
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
653
        assert(null != $streamProviderWrapper, "Retrieved stream provider must not be null");
654
        if ($resourceType->isMediaLinkEntry()) {
655
            $odataEntry->isMediaLinkEntry = true;
656
            $eTag = $streamProviderWrapper->getStreamETag($entryObject, null);
657
            $readStreamUri = $streamProviderWrapper->getReadStreamUri($entryObject, null, $relativeUri);
658
            $mediaContentType = $streamProviderWrapper->getStreamContentType($entryObject, null);
659
            $mediaLink = new ODataMediaLink(
660
                $title,
661
                $streamProviderWrapper->getDefaultStreamEditMediaUri(
662
                    $relativeUri,
663
                    null
664
                ),
665
                $readStreamUri,
666
                $mediaContentType,
667
                $eTag
668
            );
669
670
            $odataEntry->mediaLink = $mediaLink;
671
        }
672
673
        if ($resourceType->hasNamedStream()) {
674
            foreach ($resourceType->getAllNamedStreams() as $title => $resourceStreamInfo) {
675
                $eTag = $streamProviderWrapper->getStreamETag(
676
                    $entryObject,
677
                    $resourceStreamInfo
678
                );
679
                $readStreamUri = $streamProviderWrapper->getReadStreamUri(
680
                    $entryObject,
681
                    $resourceStreamInfo,
682
                    $relativeUri
683
                );
684
                $mediaContentType = $streamProviderWrapper->getStreamContentType(
685
                    $entryObject,
686
                    $resourceStreamInfo
687
                );
688
                $mediaLink = new ODataMediaLink(
689
                    $title,
690
                    $streamProviderWrapper->getReadStreamUri(
691
                        $entryObject,
692
                        $resourceStreamInfo,
693
                        $relativeUri
694
                    ),
695
                    $readStreamUri,
696
                    $mediaContentType,
697
                    $eTag
698
                );
699
700
                $odataEntry->mediaLinks[] = $mediaLink;
701
            }
702
        }
703
    }
704
705
    /**
706
     * Convert the given primitive value to string.
707
     * Note: This method will not handle null primitive value.
708
     *
709
     * @param ResourceType &$primitiveResourceType Type of the primitive property
710
     *                                             whose value need to be converted
711
     * @param mixed        $primitiveValue         Primitive value to convert
712
     *
713
     * @return string
714
     */
715
    private function _primitiveToString(
716
        ResourceType & $primitiveResourceType,
717
        $primitiveValue
718
    ) {
719
        $type = $primitiveResourceType->getInstanceType();
720
        if ($type instanceof Boolean) {
721
            $stringValue = ($primitiveValue === true) ? 'true' : 'false';
722
        } elseif ($type instanceof Binary) {
723
            $stringValue = base64_encode($primitiveValue);
724
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
725
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
726
        } elseif ($type instanceof StringType) {
727
            $stringValue = utf8_encode($primitiveValue);
728
        } else {
729
            $stringValue = strval($primitiveValue);
730
        }
731
732
        return $stringValue;
733
    }
734
735
    /**
736
     * Write value of a complex object.
737
     * Note: This method will not handle null complex value.
738
     *
739
     * @param mixed                &$complexValue         Complex object to write
740
     * @param string               $propertyName          Name of the
741
     *                                                    complex property
742
     *                                                    whose value
743
     *                                                    need to be written
744
     * @param ResourceType         &$resourceType         Expected type of the
745
     *                                                    property
746
     * @param string               $relativeUri           Relative uri for the
747
     *                                                    complex type element
748
     * @param ODataPropertyContent &$odataPropertyContent Content to write to
749
     *
750
     * @return ResourceType The actual type of the complex object
751
     */
752
    private function _complexObjectToContent(
753
        &$complexValue,
754
        $propertyName,
755
        ResourceType & $resourceType,
756
        $relativeUri,
757
        ODataPropertyContent & $odataPropertyContent
758
    ) {
759
        $count = count($this->complexTypeInstanceCollection);
760
        for ($i = 0; $i < $count; ++$i) {
761
            if ($this->complexTypeInstanceCollection[$i] === $complexValue) {
762
                throw new InvalidOperationException(
763
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
764
                );
765
            }
766
        }
767
768
        $this->complexTypeInstanceCollection[$count] = &$complexValue;
769
770
        //TODO function to resolve actual type from $resourceType
771
        $actualType = $resourceType;
772
        $odataEntry = null;
773
        $this->_writeObjectProperties(
774
            $complexValue,
775
            $actualType,
776
            null,
777
            $relativeUri,
778
            $odataEntry,
779
            $odataPropertyContent
780
        );
781
        unset($this->complexTypeInstanceCollection[$count]);
782
783
        return $actualType;
784
    }
785
786
    /**
787
     * @param $customObject
788
     * @param ResourceType $resourceType
789
     * @param string $relativeUri
790
     * @param ODataPropertyContent $odataPropertyContent
791
     * @param ResourceTypeKind $resourceTypeKind
792
     * @param $navigationProperties
793
     *
794
     * @throws ODataException
795
     *
796
     * @return array
797
     */
798
    private function writeObjectPropertiesUnexpanded(
799
        $customObject,
800
        ResourceType & $resourceType,
801
        $relativeUri,
802
        ODataPropertyContent & $odataPropertyContent,
803
        $resourceTypeKind,
804
        $navigationProperties
805
    ) {
806
        //This is the code path to handle properties of Complex type
807
        //or Entry without projection (i.e. no expansion or selection)
808
        if ($resourceTypeKind == ResourceTypeKind::ENTITY) {
809
            // If custom object is an entry then it can contain navigation
810
            // properties which are invisible (because the corresponding
811
            // resource set is invisible).
812
            // IDSMP::getResourceProperties will give collection of properties
813
            // which are visible.
814
            $currentResourceSetWrapper1 = $this->getCurrentResourceSetWrapper();
815
            $resourceProperties = $this->getService()
816
                ->getProvidersWrapper()
817
                ->getResourceProperties(
818
                    $currentResourceSetWrapper1,
819
                    $resourceType
820
                );
821
        } else {
822
            $resourceProperties = $resourceType->getAllProperties();
823
        }
824
825
        //First write out primitive types
826
        foreach ($resourceProperties as $name => $resourceProperty) {
827
            $resourceKind = $resourceProperty->getKind();
828
            if (ObjectModelSerializer::isMatchPrimitive($resourceKind)) {
829
                $odataProperty = new ODataProperty();
830
                $primitiveValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
831
                $this->_writePrimitiveValue($primitiveValue, $resourceProperty, $odataProperty);
832
                $odataPropertyContent->properties[] = $odataProperty;
833
            }
834
        }
835
836
        //Write out bag and complex type
837
        $i = 0;
838
        foreach ($resourceProperties as $resourceProperty) {
839
            if ($resourceProperty->isKindOf(ResourcePropertyKind::BAG)) {
840
                //Handle Bag Property (Bag of Primitive or complex)
841
                $propertyValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
842
                $resourceType2 = $resourceProperty->getResourceType();
843
                $this->_writeBagValue(
844
                    $propertyValue,
845
                    $resourceProperty->getName(),
846
                    $resourceType2,
847
                    $relativeUri . '/' . $resourceProperty->getName(),
848
                    $odataPropertyContent
849
                );
850
            } else {
851
                $resourceKind = $resourceProperty->getKind();
852
                if ($resourceKind == ResourcePropertyKind::COMPLEX_TYPE) {
853
                    $propertyValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
854
                    $resourceType1 = $resourceProperty->getResourceType();
855
                    $this->_writeComplexValue(
856
                        $propertyValue,
857
                        $resourceProperty->getName(),
858
                        $resourceType1,
859
                        $relativeUri . '/' . $resourceProperty->getName(),
860
                        $odataPropertyContent
861
                    );
862
                } elseif (ObjectModelSerializer::isMatchPrimitive($resourceKind)) {
863
                    continue;
864
                } else {
865
                    assert(
866
                        ($resourceKind == ResourcePropertyKind::RESOURCE_REFERENCE)
867
                        || ($resourceKind == ResourcePropertyKind::RESOURCESET_REFERENCE),
868
                        '($resourceKind != ResourcePropertyKind::RESOURCE_REFERENCE)'
869
                        .'&& ($resourceKind != ResourcePropertyKind::RESOURCESET_REFERENCE)'
870
                    );
871
872
                    $navigationProperties[$i] = new ODataNavigationPropertyInfo(
873
                        $resourceProperty,
874
                        $this->shouldExpandSegment($resourceProperty->getName())
875
                    );
876
                    if ($navigationProperties[$i]->expanded) {
877
                        $navigationProperties[$i]->value = $this->getPropertyValue(
878
                            $customObject,
879
                            $resourceType,
880
                            $resourceProperty
881
                        );
882
                    }
883
884
                    ++$i;
885
                }
886
            }
887
        }
888
889
        return [$odataPropertyContent, $navigationProperties];
890
    }
891
892
    public static function isMatchPrimitive($resourceKind)
893
    {
894
        if (16 > $resourceKind) {
895
            return false;
896
        }
897
        if (28 < $resourceKind) {
898
            return false;
899
        }
900
        return 0 == ($resourceKind % 4);
901
    }
902
903
904
    /**
905
     * @param $customObject
906
     * @param ResourceType $resourceType
907
     * @param string $relativeUri
908
     * @param ODataPropertyContent $odataPropertyContent
909
     * @param \POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode[] $projectionNodes
910
     * @param $navigationProperties
911
     *
912
     * @throws ODataException
913
     *
914
     * @return array
915
     */
916
    private function writeObjectPropertiesExpanded(
917
        $customObject,
918
        ResourceType & $resourceType,
919
        $relativeUri,
920
        ODataPropertyContent & $odataPropertyContent,
921
        $projectionNodes,
922
        $navigationProperties
923
    ) {
924
        $i = 0;
925
        foreach ($projectionNodes as $projectionNode) {
926
            $propertyName = $projectionNode->getPropertyName();
927
            $resourceProperty = $resourceType->resolveProperty($propertyName);
928
            assert(!is_null($resourceProperty), 'is_null($resourceProperty)');
929
930
            if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY) {
931
                $currentResourceSetWrapper2 = $this->getCurrentResourceSetWrapper();
932
                $resourceProperties = $this->getService()
933
                    ->getProvidersWrapper()
934
                    ->getResourceProperties(
935
                        $currentResourceSetWrapper2,
936
                        $resourceType
937
                    );
938
                //Check for the visibility of this navigation property
939
                if (array_key_exists($resourceProperty->getName(), $resourceProperties)) {
940
                    $navigationProperties[$i] = new ODataNavigationPropertyInfo(
941
                        $resourceProperty,
942
                        $this->shouldExpandSegment($propertyName)
943
                    );
944
                    if ($navigationProperties[$i]->expanded) {
945
                        $navigationProperties[$i]->value = $this->getPropertyValue(
946
                            $customObject,
947
                            $resourceType,
948
                            $resourceProperty
949
                        );
950
                    }
951
952
                    ++$i;
953
                    continue;
954
                }
955
            }
956
957
            //Primitive, complex or bag property
958
            $propertyValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
959
            $propertyTypeKind = $resourceProperty->getKind();
960
            $propertyResourceType = $resourceProperty->getResourceType();
961
            assert(!is_null($propertyResourceType), 'is_null($propertyResourceType)');
962
            if ($resourceProperty->isKindOf(ResourcePropertyKind::BAG)) {
963
                $bagResourceType = $resourceProperty->getResourceType();
964
                $this->_writeBagValue(
965
                    $propertyValue,
966
                    $propertyName,
967
                    $bagResourceType,
968
                    $relativeUri . '/' . $propertyName,
969
                    $odataPropertyContent
970
                );
971
            } elseif ($resourceProperty->isKindOf(ResourcePropertyKind::PRIMITIVE)) {
972
                $odataProperty = new ODataProperty();
973
                $this->_writePrimitiveValue($propertyValue, $resourceProperty, $odataProperty);
974
                $odataPropertyContent->properties[] = $odataProperty;
975
            } elseif ($propertyTypeKind == ResourcePropertyKind::COMPLEX_TYPE) {
976
                $complexResourceType = $resourceProperty->getResourceType();
977
                $this->_writeComplexValue(
978
                    $propertyValue,
979
                    $propertyName,
980
                    $complexResourceType,
981
                    $relativeUri . '/' . $propertyName,
982
                    $odataPropertyContent
983
                );
984
            } else {
985
                //unexpected
986
                assert(false, '$propertyTypeKind != Primitive or Bag or ComplexType');
987
            }
988
        }
989
990
        return [$navigationProperties, $odataPropertyContent];
991
    }
992
993
}
994