Passed
Push — master ( 492d83...a726fe )
by Alex
01:10
created

ObjectModelSerializer::_writeObjectProperties()   D

Complexity

Conditions 14
Paths 41

Size

Total Lines 112
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 112
rs 4.9516
c 0
b 0
f 0
cc 14
eloc 84
nc 41
nop 6

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 (TargetSource::ENTITY_SET == $requestTargetSource) {
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(TargetSource::PROPERTY == $requestTargetSource, '$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 (TargetSource::ENTITY_SET == $requestTargetSource) {
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(TargetSource::PROPERTY == $requestTargetSource, '$requestTargetSource != TargetSource::PROPERTY');
85
            $resourceProperty = $this->getRequest()->getProjectedProperty();
86
            assert(
87
                ResourcePropertyKind::RESOURCESET_REFERENCE == $resourceProperty->getKind(),
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 = null
246
    ) {
247
        assert(null != $resourceProperty, "Resource property must not be null");
248
        $propertyContent = new ODataPropertyContent();
249
        $propertyContent->properties[] = new ODataProperty();
250
        $this->writePrimitiveValue(
251
            $primitiveValue,
252
            $propertyContent->properties[0],
253
            $resourceProperty
254
        );
255
256
        return $propertyContent;
257
    }
258
259
    /**
260
     * Write an entry element.
261
     *
262
     * @param mixed        $entryObject  Object representing entry element
263
     * @param ResourceType $resourceType Expected type of the entry object
264
     * @param string       $absoluteUri  Absolute uri of the entry element
265
     * @param string       $relativeUri  Relative uri of the entry element
266
     *
267
     * @return ODataEntry
268
     */
269
    private function writeEntryElement(
270
        $entryObject,
271
        ResourceType $resourceType,
272
        $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...
273
        $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...
274
    ) {
275
        $entry = new ODataEntry();
276
        $entry->resourceSetName = $this->getCurrentResourceSetWrapper()->getName();
277
278
        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...
279
            //According to atom standard an empty entry must have an Author
280
            //node.
281
        } else {
282
            $relativeUri = $this->getEntryInstanceKey(
283
                $entryObject,
284
                $resourceType,
285
                $this->getCurrentResourceSetWrapper()->getName()
286
            );
287
288
            $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
289
            $title = $resourceType->getName();
290
            //TODO Resolve actual resource type
291
            $actualResourceType = $resourceType;
292
            $this->writeMediaResourceMetadata(
293
                $entryObject,
294
                $actualResourceType,
295
                $title,
296
                $relativeUri,
297
                $entry
298
            );
299
300
            $entry->id = $absoluteUri;
301
            $entry->eTag = $this->getETagForEntry($entryObject, $resourceType);
302
            $entry->title = $title;
303
            $entry->editLink = $relativeUri;
304
            $entry->type = $actualResourceType->getFullName();
305
            $odataPropertyContent = new ODataPropertyContent();
306
            $this->writeObjectProperties(
307
                $entryObject,
308
                $actualResourceType,
309
                $absoluteUri,
310
                $relativeUri,
311
                $entry,
312
                $odataPropertyContent
313
            );
314
            $entry->propertyContent = $odataPropertyContent;
315
        }
316
317
        return $entry;
318
    }
319
320
    /**
321
     * Writes the feed elements.
322
     *
323
     * @param array        &$entryObjects Array of entries in the feed element
324
     * @param ResourceType &$resourceType The resource type of the f the elements
325
     *                                    in the collection
326
     * @param string       $title         Title of the feed element
327
     * @param string       $absoluteUri   Absolute uri representing the feed element
328
     * @param string       $relativeUri   Relative uri representing the feed element
329
     * @param ODataFeed    &$feed         Feed to write to
330
     */
331
    private function writeFeedElements(
332
        &$entryObjects,
333
        ResourceType &$resourceType,
334
        $title,
335
        $absoluteUri,
336
        $relativeUri,
337
        ODataFeed &$feed
338
    ) {
339
        assert(is_array($entryObjects), '!_writeFeedElements::is_array($entryObjects)');
340
        $feed->id = $absoluteUri;
341
        $feed->title = $title;
342
        $feed->selfLink = new ODataLink();
343
        $feed->selfLink->name = ODataConstants::ATOM_SELF_RELATION_ATTRIBUTE_VALUE;
344
        $feed->selfLink->title = $title;
345
        $feed->selfLink->url = $relativeUri;
346
347
        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...
348
            //TODO // ATOM specification: if a feed contains no entries,
349
            //then the feed should have at least one Author tag
350
        } else {
351
            foreach ($entryObjects as $entryObject) {
352
                $feed->entries[] = $this->writeEntryElement($entryObject, $resourceType, null, null);
353
            }
354
355
            if ($this->needNextPageLink(count($entryObjects))) {
356
                $lastObject = end($entryObjects);
357
                $feed->nextPageLink = $this->getNextLinkUri($lastObject, $absoluteUri);
358
            }
359
        }
360
    }
361
362
    /**
363
     * Write values of properties of given entry (resource) or complex object.
364
     *
365
     * @param mixed        $customObject  Entity or complex object
366
     *                                    with properties
367
     *                                    to write out
368
     * @param ResourceType &$resourceType Resource type describing
369
     *                                    the metadata of
370
     *                                    the custom object
371
     * @param string       $absoluteUri   Absolute uri for the given
372
     *                                    entry object
373
     *                                    NULL for complex object
374
     * @param string       $relativeUri   Relative uri for the given
375
     *                                    custom object
376
     * @param ODataEntry           ODataEntry|null           ODataEntry instance to
377
     *                                                    place links and
378
     *                                                    expansion of the
379
     *                                                    entry object,
380
     *                                                    NULL for complex object
381
     * @param ODataPropertyContent &$odataPropertyContent ODataPropertyContent
382
     *                                                    instance in which
383
     *                                                    to place the values
384
     */
385
    private function writeObjectProperties(
386
        $customObject,
387
        ResourceType &$resourceType,
388
        $absoluteUri,
389
        $relativeUri,
390
        &$odataEntry,
391
        ODataPropertyContent &$odataPropertyContent
392
    ) {
393
        $resourceTypeKind = $resourceType->getResourceTypeKind();
394
        if (is_null($absoluteUri) == (ResourceTypeKind::ENTITY == $resourceTypeKind)
395
        ) {
396
            throw ODataException::createInternalServerError(
397
                Messages::badProviderInconsistentEntityOrComplexTypeUsage(
398
                    $resourceType->getName()
399
                )
400
            );
401
        }
402
403
        assert(
404
            ((ResourceTypeKind::ENTITY == $resourceTypeKind) && ($odataEntry instanceof ODataEntry))
405
            || ((ResourceTypeKind::COMPLEX == $resourceTypeKind) && is_null($odataEntry)),
406
            '!(($resourceTypeKind == ResourceTypeKind::ENTITY) && ($odataEntry instanceof ODataEntry))'
407
            .' && !(($resourceTypeKind == ResourceTypeKind::COMPLEX) && is_null($odataEntry))'
408
        );
409
        $projectionNodes = null;
410
        $navigationProperties = null;
411
        if (ResourceTypeKind::ENTITY == $resourceTypeKind) {
412
            $projectionNodes = $this->getProjectionNodes();
413
            $navigationProperties = [];
414
        }
415
416
        if (is_null($projectionNodes)) {
417
            list($odataPropertyContent, $navigationProperties) = $this->writeObjectPropertiesUnexpanded(
418
                $customObject,
419
                $resourceType,
420
                $relativeUri,
421
                $odataPropertyContent,
422
                $resourceTypeKind,
423
                $navigationProperties
424
            );
425
        } else { //This is the code path to handle projected properties of Entry
426
            list($navigationProperties, $odataPropertyContent) = $this->writeObjectPropertiesExpanded(
427
                $customObject,
428
                $resourceType,
429
                $relativeUri,
430
                $odataPropertyContent,
431
                $projectionNodes,
432
                $navigationProperties
433
            );
434
        }
435
436
        if (!is_null($navigationProperties)) {
437
            //Write out navigation properties (deferred or inline)
438
            foreach ($navigationProperties as $navigationPropertyInfo) {
439
                $propertyName = $navigationPropertyInfo->resourceProperty->getName();
440
                $type = ResourcePropertyKind::RESOURCE_REFERENCE == $navigationPropertyInfo->resourceProperty->getKind() ?
441
                    'application/atom+xml;type=entry' : 'application/atom+xml;type=feed';
442
                $link = new ODataLink();
443
                $link->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propertyName;
444
                $link->title = $propertyName;
445
                $link->type = $type;
446
                $link->url = $relativeUri . '/' . $propertyName;
447
448
                if ($navigationPropertyInfo->expanded) {
449
                    $propertyRelativeUri = $relativeUri . '/' . $propertyName;
450
                    $propertyAbsoluteUri = trim($absoluteUri, '/') . '/' . $propertyName;
451
                    $needPop = $this->pushSegmentForNavigationProperty($navigationPropertyInfo->resourceProperty);
452
                    $navigationPropertyKind = $navigationPropertyInfo->resourceProperty->getKind();
453
                    assert(
454
                        ResourcePropertyKind::RESOURCESET_REFERENCE == $navigationPropertyKind
455
                        || ResourcePropertyKind::RESOURCE_REFERENCE == $navigationPropertyKind,
456
                        '$navigationPropertyKind != ResourcePropertyKind::RESOURCESET_REFERENCE 
457
                        && $navigationPropertyKind != ResourcePropertyKind::RESOURCE_REFERENCE'
458
                    );
459
                    $currentResourceSetWrapper = $this->getCurrentResourceSetWrapper();
460
                    assert(!is_null($currentResourceSetWrapper), 'is_null($currentResourceSetWrapper)');
461
                    $link->isExpanded = true;
462
                    if (!is_null($navigationPropertyInfo->value)) {
463
                        $currentResourceType = $currentResourceSetWrapper->getResourceType();
464
                        if (ResourcePropertyKind::RESOURCESET_REFERENCE == $navigationPropertyKind) {
465
                            $inlineFeed = new ODataFeed();
466
                            $link->isCollection = true;
467
468
                            $this->writeFeedElements(
469
                                $navigationPropertyInfo->value,
470
                                $currentResourceType,
471
                                $propertyName,
472
                                $propertyAbsoluteUri,
473
                                $propertyRelativeUri,
474
                                $inlineFeed
475
                            );
476
                            $link->expandedResult = $inlineFeed;
477
                        } else {
478
                            $link->isCollection = false;
479
                            $link->expandedResult = $this->writeEntryElement(
480
                                $navigationPropertyInfo->value,
481
                                $currentResourceType,
482
                                $propertyAbsoluteUri,
483
                                $propertyRelativeUri
484
                            );
485
                        }
486
                    } else {
487
                        $link->expandedResult = null;
488
                    }
489
490
                    $this->popSegment($needPop);
491
                }
492
493
                $odataEntry->links[] = $link;
494
            }
495
        }
496
    }
497
498
    /**
499
     * Writes a primitive value and related information to the given
500
     * ODataProperty instance.
501
     *
502
     * @param mixed &$primitiveValue The primitive value to write
503
     * @param ODataProperty &$odataProperty ODataProperty instance to which
504
     *                                            the primitive value and related
505
     *                                            information to write out
506
     *
507
     * @param ResourceProperty|null &$resourceProperty The metadata of the primitive
508
     *                                            property value
509
     */
510
    private function writePrimitiveValue(
511
        &$primitiveValue,
512
        ODataProperty &$odataProperty,
513
        ResourceProperty &$resourceProperty
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
            ResourceTypeKind::PRIMITIVE == $bagItemResourceTypeKind
596
            || ResourceTypeKind::COMPLEX == $bagItemResourceTypeKind,
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 (ResourceTypeKind::PRIMITIVE == $bagItemResourceTypeKind) {
613
                    $odataBagContent->propertyContents[] = $this->primitiveToString($resourceType, $itemValue);
614
                } elseif (ResourceTypeKind::COMPLEX == $bagItemResourceTypeKind) {
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::ENTITY == $resourceTypeKind) {
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, $odataProperty, $resourceProperty);
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 (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
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
                        (ResourcePropertyKind::RESOURCE_REFERENCE == $resourceKind)
867
                        || (ResourcePropertyKind::RESOURCESET_REFERENCE == $resourceKind),
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 (ResourceTypeKind::ENTITY == $resourceProperty->getTypeKind()) {
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, $odataProperty, $resourceProperty);
974
                $odataPropertyContent->properties[] = $odataProperty;
975
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $propertyTypeKind) {
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