Test Setup Failed
Push — master ( a205ed...3608af )
by Alex
03:32
created

writeObjectPropertiesUnexpanded()   D

Complexity

Conditions 9
Paths 30

Size

Total Lines 95
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 95
rs 4.9931
cc 9
eloc 65
nc 30
nop 6

How to fix   Long Method   

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\QueryResult;
20
use POData\Providers\Query\QueryType;
21
use POData\UriProcessor\RequestDescription;
22
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
23
24
/**
25
 * Class ObjectModelSerializer.
26
 */
27
class ObjectModelSerializer extends ObjectModelSerializerBase implements IObjectSerialiser
28
{
29
    /**
30
     * Creates new instance of ObjectModelSerializer.
31
     *
32
     * @param IService           $service
33
     * @param RequestDescription $request the  request submitted by the client
34
     */
35
    public function __construct(IService $service, RequestDescription $request = null)
36
    {
37
        parent::__construct($service, $request);
38
    }
39
40
    /**
41
     * Write a top level entry resource.
42
     *
43
     * @param QueryResult $entryObject      Results property contains reference to the entry object to be written
44
     *
45
     * @return ODataEntry
46
     */
47
    public function writeTopLevelElement(QueryResult $entryObject)
48
    {
49
        $requestTargetSource = $this->getRequest()->getTargetSource();
50
51 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...
52
            $resourceType = $this->getRequest()->getTargetResourceType();
53
        } else {
54
            assert(TargetSource::PROPERTY == $requestTargetSource, '$requestTargetSource != TargetSource::PROPERTY');
55
            $resourceProperty = $this->getRequest()->getProjectedProperty();
56
            $resourceType = $resourceProperty->getResourceType();
57
        }
58
59
        $needPop = $this->pushSegmentForRoot();
60
        $entry = $this->writeEntryElement(
61
            $entryObject->results,
62
            $resourceType,
63
            $this->getRequest()->getRequestUrl()->getUrlAsString(),
64
            $this->getRequest()->getContainerName()
65
        );
66
        $this->popSegment($needPop);
67
68
        return $entry;
69
    }
70
71
    /**
72
     * Write top level feed element.
73
     *
74
     * @param QueryResult &$entryObjects    Results property contains array of entry resources to be written
75
     *
76
     * @return ODataFeed
77
     */
78
    public function writeTopLevelElements(QueryResult &$entryObjects)
79
    {
80
        assert(is_array($entryObjects->results), '!is_array($entryObjects->results)');
81
        $requestTargetSource = $this->getRequest()->getTargetSource();
82
        if (TargetSource::ENTITY_SET == $requestTargetSource) {
83
            $title = $this->getRequest()->getContainerName();
84 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...
85
            assert(TargetSource::PROPERTY == $requestTargetSource, '$requestTargetSource != TargetSource::PROPERTY');
86
            $resourceProperty = $this->getRequest()->getProjectedProperty();
87
            assert(
88
                ResourcePropertyKind::RESOURCESET_REFERENCE == $resourceProperty->getKind(),
89
                '$resourceProperty->getKind() != ResourcePropertyKind::RESOURCESET_REFERENCE'
90
            );
91
            $title = $resourceProperty->getName();
92
        }
93
94
        $relativeUri = $this->getRequest()->getIdentifier();
95
        $feed = new ODataFeed();
96
97
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
98
            $feed->rowCount = $this->getRequest()->getCountValue();
99
        }
100
101
        $needPop = $this->pushSegmentForRoot();
102
        $targetResourceType = $this->getRequest()->getTargetResourceType();
103
        assert(null != $targetResourceType, 'Target resource type must not be null');
104
105
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
106
        $requestTop = $this->getRequest()->getTopOptionCount();
107
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
108
        $requestTop = (null == $requestTop) ? $pageSize + 1 : $requestTop;
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $requestTop of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
109
        $needLink = $entryObjects->hasMore && ($requestTop > $pageSize);
110
111
        $this->writeFeedElements(
112
            $entryObjects->results,
113
            $targetResourceType,
114
            $title,
115
            $this->getRequest()->getRequestUrl()->getUrlAsString(),
116
            $relativeUri,
117
            $feed,
118
            $needLink
119
        );
120
        $this->popSegment($needPop);
121
122
        return $feed;
123
    }
124
125
    /**
126
     * Write top level url element.
127
     *
128
     * @param QueryResult $entryObject      Results property contains the entry resource whose url to be written
129
     *
130
     * @return ODataURL
131
     */
132
    public function writeUrlElement(QueryResult $entryObject)
133
    {
134
        $url = new ODataURL();
135
        if (!is_null($entryObject->results)) {
136
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
137
            $relativeUri = $this->getEntryInstanceKey(
138
                $entryObject->results,
139
                $currentResourceType,
140
                $this->getCurrentResourceSetWrapper()->getName()
141
            );
142
143
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
144
        }
145
146
        return $url;
147
    }
148
149
    /**
150
     * Write top level url collection.
151
     *
152
     * @param QueryResult $entryObjects     Results property contains the array of entry resources whose urls are
153
     *                                      to be written
154
     *
155
     * @return ODataURLCollection
156
     */
157
    public function writeUrlElements(QueryResult $entryObjects)
158
    {
159
        $urls = new ODataURLCollection();
160
        $results = $entryObjects->results;
161
        if (!empty($results)) {
162
            $i = 0;
163
            foreach ($results as $entryObject) {
164
                $urls->urls[$i] = $this->writeUrlElement($entryObject);
165
                ++$i;
166
            }
167
168
            //if ($i > 0 && $this->needNextPageLink(count($results))) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
169
            if ($i > 0 && true === $entryObjects->hasMore) {
170
                $urls->nextPageLink = $this->getNextLinkUri(
171
                    $results[$i - 1],
172
                    $this->getRequest()->getRequestUrl()->getUrlAsString()
173
                );
174
            }
175
        }
176
177
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
178
            $urls->count = $this->getRequest()->getCountValue();
179
        }
180
181
        return $urls;
182
    }
183
184
    /**
185
     * Write top level complex resource.
186
     *
187
     * @param QueryResult  &$complexValue Results property contains the complex object to be written
188
     * @param string       $propertyName  The name of the complex property
189
     * @param ResourceType &$resourceType Describes the type of complex object
190
     *
191
     * @return ODataPropertyContent
192
     */
193 View Code Duplication
    public function writeTopLevelComplexObject(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
194
        QueryResult &$complexValue,
195
        $propertyName,
196
        ResourceType & $resourceType
197
    ) {
198
        $propertyContent = new ODataPropertyContent();
199
        $this->writeComplexValue(
200
            $complexValue->results,
201
            $propertyName,
202
            $resourceType,
203
            null,
204
            $propertyContent
205
        );
206
207
        return $propertyContent;
208
    }
209
210
    /**
211
     * Write top level bag resource.
212
     *
213
     * @param QueryResult  &$BagValue     Results property contains the bag object to be written
214
     * @param string       $propertyName  The name of the bag property
215
     * @param ResourceType &$resourceType Describes the type of bag object
216
     *
217
     * @return ODataPropertyContent
218
     */
219 View Code Duplication
    public function writeTopLevelBagObject(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
220
        QueryResult &$BagValue,
221
        $propertyName,
222
        ResourceType &$resourceType
223
    ) {
224
        $propertyContent = new ODataPropertyContent();
225
        $this->writeBagValue(
226
            $BagValue->results,
227
            $propertyName,
228
            $resourceType,
229
            null,
230
            $propertyContent
231
        );
232
233
        return $propertyContent;
234
    }
235
236
    /**
237
     * Write top level primitive value.
238
     *
239
     * @param QueryResult      &$primitiveValue   Results property contains the primitive value to be written
240
     * @param ResourceProperty &$resourceProperty Resource property describing the primitive property to be written
241
     *
242
     * @return ODataPropertyContent
243
     */
244
    public function writeTopLevelPrimitive(
245
        QueryResult &$primitiveValue,
246
        ResourceProperty &$resourceProperty = null
247
    ) {
248
        assert(null != $resourceProperty, "Resource property must not be null");
249
        $propertyContent = new ODataPropertyContent();
250
        $propertyContent->properties[] = new ODataProperty();
251
        $this->writePrimitiveValue(
252
            $primitiveValue->results,
253
            $propertyContent->properties[0],
254
            $resourceProperty
255
        );
256
257
        return $propertyContent;
258
    }
259
260
    /**
261
     * Write an entry element.
262
     *
263
     * @param mixed        $entryObject  Object representing entry element
264
     * @param ResourceType $resourceType Expected type of the entry object
265
     * @param string       $absoluteUri  Absolute uri of the entry element
266
     * @param string       $relativeUri  Relative uri of the entry element
267
     *
268
     * @return ODataEntry
269
     */
270
    private function writeEntryElement(
271
        $entryObject,
272
        ResourceType $resourceType,
273
        $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...
274
        $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...
275
    ) {
276
        $entry = new ODataEntry();
277
        $entry->resourceSetName = $this->getCurrentResourceSetWrapper()->getName();
278
279
        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...
280
            //According to atom standard an empty entry must have an Author
281
            //node.
282
        } else {
283
            $relativeUri = $this->getEntryInstanceKey(
284
                $entryObject,
285
                $resourceType,
286
                $this->getCurrentResourceSetWrapper()->getName()
287
            );
288
289
            $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
290
            $title = $resourceType->getName();
291
            //TODO Resolve actual resource type
292
            $actualResourceType = $resourceType;
293
            $this->writeMediaResourceMetadata(
294
                $entryObject,
295
                $actualResourceType,
296
                $title,
297
                $relativeUri,
298
                $entry
299
            );
300
301
            $entry->id = $absoluteUri;
302
            $entry->eTag = $this->getETagForEntry($entryObject, $resourceType);
303
            $entry->title = $title;
304
            $entry->editLink = $relativeUri;
305
            $entry->type = $actualResourceType->getFullName();
306
            $odataPropertyContent = new ODataPropertyContent();
307
            $this->writeObjectProperties(
308
                $entryObject,
309
                $actualResourceType,
310
                $absoluteUri,
311
                $relativeUri,
312
                $entry,
313
                $odataPropertyContent
314
            );
315
            $entry->propertyContent = $odataPropertyContent;
316
        }
317
318
        return $entry;
319
    }
320
321
    /**
322
     * Writes the feed elements.
323
     *
324
     * @param array        &$entryObjects Array of entries in the feed element
325
     * @param ResourceType &$resourceType The resource type of the elements 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
     * @param bool|null    $needLink      Has query provider already determined next-page link is needed?
331
     */
332
    private function writeFeedElements(
333
        &$entryObjects,
334
        ResourceType &$resourceType,
335
        $title,
336
        $absoluteUri,
337
        $relativeUri,
338
        ODataFeed &$feed,
339
        $needLink = false
340
    ) {
341
        assert(is_array($entryObjects), '!_writeFeedElements::is_array($entryObjects)');
342
        $feed->id = $absoluteUri;
343
        $feed->title = $title;
344
        $feed->selfLink = new ODataLink();
345
        $feed->selfLink->name = ODataConstants::ATOM_SELF_RELATION_ATTRIBUTE_VALUE;
346
        $feed->selfLink->title = $title;
347
        $feed->selfLink->url = $relativeUri;
348
349
        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...
350
            //TODO // ATOM specification: if a feed contains no entries,
351
            //then the feed should have at least one Author tag
352
        } else {
353
            foreach ($entryObjects as $entryObject) {
354
                if ($entryObject instanceof QueryResult) {
355
                    $entryObject = $entryObject->results;
356
                }
357
                $feed->entries[] = $this->writeEntryElement($entryObject, $resourceType, null, null);
358
            }
359
360
            if (true === $needLink) {
361
                if ($entryObjects instanceof QueryResult) {
362
                    $entryObjects = $entryObjects->results;
363
                }
364
                $lastObject = end($entryObjects);
365
                $feed->nextPageLink = $this->getNextLinkUri($lastObject, $absoluteUri);
366
            }
367
        }
368
    }
369
370
    /**
371
     * Write values of properties of given entry (resource) or complex object.
372
     *
373
     * @param mixed        $customObject  Entity or complex object
374
     *                                    with properties
375
     *                                    to write out
376
     * @param ResourceType &$resourceType Resource type describing
377
     *                                    the metadata of
378
     *                                    the custom object
379
     * @param string       $absoluteUri   Absolute uri for the given
380
     *                                    entry object
381
     *                                    NULL for complex object
382
     * @param string       $relativeUri   Relative uri for the given
383
     *                                    custom object
384
     * @param ODataEntry           ODataEntry|null           ODataEntry instance to
385
     *                                                    place links and
386
     *                                                    expansion of the
387
     *                                                    entry object,
388
     *                                                    NULL for complex object
389
     * @param ODataPropertyContent &$odataPropertyContent ODataPropertyContent
390
     *                                                    instance in which
391
     *                                                    to place the values
392
     */
393
    private function writeObjectProperties(
394
        $customObject,
395
        ResourceType &$resourceType,
396
        $absoluteUri,
397
        $relativeUri,
398
        &$odataEntry,
399
        ODataPropertyContent &$odataPropertyContent
400
    ) {
401
        $resourceTypeKind = $resourceType->getResourceTypeKind();
402
        if (is_null($absoluteUri) == (ResourceTypeKind::ENTITY == $resourceTypeKind)
403
        ) {
404
            throw ODataException::createInternalServerError(
405
                Messages::badProviderInconsistentEntityOrComplexTypeUsage(
406
                    $resourceType->getName()
407
                )
408
            );
409
        }
410
411
        assert(
412
            ((ResourceTypeKind::ENTITY == $resourceTypeKind) && ($odataEntry instanceof ODataEntry))
413
            || ((ResourceTypeKind::COMPLEX == $resourceTypeKind) && is_null($odataEntry)),
414
            '!(($resourceTypeKind == ResourceTypeKind::ENTITY) && ($odataEntry instanceof ODataEntry))'
415
            .' && !(($resourceTypeKind == ResourceTypeKind::COMPLEX) && is_null($odataEntry))'
416
        );
417
        $projectionNodes = null;
418
        $navigationProperties = null;
419
        if (ResourceTypeKind::ENTITY == $resourceTypeKind) {
420
            $projectionNodes = $this->getProjectionNodes();
421
            $navigationProperties = [];
422
        }
423
424
        if (is_null($projectionNodes)) {
425
            list($odataPropertyContent, $navigationProperties) = $this->writeObjectPropertiesUnexpanded(
426
                $customObject,
427
                $resourceType,
428
                $relativeUri,
429
                $odataPropertyContent,
430
                $resourceTypeKind,
431
                $navigationProperties
432
            );
433
        } else { //This is the code path to handle projected properties of Entry
434
            list($odataPropertyContent, $navigationProperties) = $this->writeObjectPropertiesExpanded(
435
                $customObject,
436
                $resourceType,
437
                $relativeUri,
438
                $odataPropertyContent,
439
                $projectionNodes,
440
                $navigationProperties
441
            );
442
        }
443
444
        if (!is_null($navigationProperties)) {
445
            //Write out navigation properties (deferred or inline)
446
            foreach ($navigationProperties as $navigationPropertyInfo) {
447
                $propertyName = $navigationPropertyInfo->resourceProperty->getName();
448
                $type = ResourcePropertyKind::RESOURCE_REFERENCE == $navigationPropertyInfo->resourceProperty->getKind() ?
449
                    'application/atom+xml;type=entry' : 'application/atom+xml;type=feed';
450
                $link = new ODataLink();
451
                $link->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propertyName;
452
                $link->title = $propertyName;
453
                $link->type = $type;
454
                $link->url = $relativeUri . '/' . $propertyName;
455
456
                if ($navigationPropertyInfo->expanded) {
457
                    $propertyRelativeUri = $relativeUri . '/' . $propertyName;
458
                    $propertyAbsoluteUri = trim($absoluteUri, '/') . '/' . $propertyName;
459
                    $needPop = $this->pushSegmentForNavigationProperty($navigationPropertyInfo->resourceProperty);
460
                    $navigationPropertyKind = $navigationPropertyInfo->resourceProperty->getKind();
461
                    assert(
462
                        ResourcePropertyKind::RESOURCESET_REFERENCE == $navigationPropertyKind
463
                        || ResourcePropertyKind::RESOURCE_REFERENCE == $navigationPropertyKind,
464
                        '$navigationPropertyKind != ResourcePropertyKind::RESOURCESET_REFERENCE 
465
                        && $navigationPropertyKind != ResourcePropertyKind::RESOURCE_REFERENCE'
466
                    );
467
                    $currentResourceSetWrapper = $this->getCurrentResourceSetWrapper();
468
                    assert(!is_null($currentResourceSetWrapper), 'is_null($currentResourceSetWrapper)');
469
                    $link->isExpanded = true;
470
                    if (!is_null($navigationPropertyInfo->value)) {
471
                        $currentResourceType = $currentResourceSetWrapper->getResourceType();
472
                        if (ResourcePropertyKind::RESOURCESET_REFERENCE == $navigationPropertyKind) {
473
                            $inlineFeed = new ODataFeed();
474
                            $link->isCollection = true;
475
476
                            //TODO: Robustise need-next-link determination
477
                            $this->writeFeedElements(
478
                                $navigationPropertyInfo->value,
479
                                $currentResourceType,
480
                                $propertyName,
481
                                $propertyAbsoluteUri,
482
                                $propertyRelativeUri,
483
                                $inlineFeed,
484
                                false
485
                            );
486
                            $link->expandedResult = $inlineFeed;
487
                        } else {
488
                            $link->isCollection = false;
489
                            $link->expandedResult = $this->writeEntryElement(
490
                                $navigationPropertyInfo->value,
491
                                $currentResourceType,
492
                                $propertyAbsoluteUri,
493
                                $propertyRelativeUri
494
                            );
495
                        }
496
                    } else {
497
                        $link->expandedResult = null;
498
                    }
499
500
                    $this->popSegment($needPop);
501
                }
502
503
                $odataEntry->links[] = $link;
504
            }
505
        }
506
    }
507
508
    /**
509
     * Writes a primitive value and related information to the given
510
     * ODataProperty instance.
511
     *
512
     * @param mixed &$primitiveValue The primitive value to write
513
     * @param ODataProperty &$odataProperty ODataProperty instance to which
514
     *                                            the primitive value and related
515
     *                                            information to write out
516
     *
517
     * @param ResourceProperty|null &$resourceProperty The metadata of the primitive
518
     *                                            property value
519
     */
520
    private function writePrimitiveValue(
521
        &$primitiveValue,
522
        ODataProperty &$odataProperty,
523
        ResourceProperty &$resourceProperty
524
    ) {
525
        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...
526
            //TODO ERROR: The property 'PropertyName'
527
            //is defined as primitive type but value is an object
528
        }
529
530
        $odataProperty->name = $resourceProperty->getName();
531
        $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...
532
        if (is_null($primitiveValue)) {
533
            $odataProperty->value = null;
534
        } else {
535
            $resourceType = $resourceProperty->getResourceType();
536
            $odataProperty->value = $this->primitiveToString($resourceType, $primitiveValue);
537
        }
538
    }
539
540
    /**
541
     * Write value of a complex object.
542
     *
543
     * @param mixed                &$complexValue         Complex object to write
544
     * @param string               $propertyName          Name of the
545
     *                                                    complex property
546
     *                                                    whose value need
547
     *                                                    to be written
548
     * @param ResourceType         &$resourceType         Expected type
549
     *                                                    of the property
550
     * @param string               $relativeUri           Relative uri for the
551
     *                                                    complex type element
552
     * @param ODataPropertyContent &$odataPropertyContent Content to write to
553
     */
554
    private function writeComplexValue(
555
        &$complexValue,
556
        $propertyName,
557
        ResourceType &$resourceType,
558
        $relativeUri,
559
        ODataPropertyContent &$odataPropertyContent
560
    ) {
561
        $odataProperty = new ODataProperty();
562
        $odataProperty->name = $propertyName;
563
        if (is_null($complexValue)) {
564
            $odataProperty->value = null;
565
            $odataProperty->typeName = $resourceType->getFullName();
566
        } else {
567
            $content = new ODataPropertyContent();
568
            $actualType = $this->complexObjectToContent(
569
                $complexValue,
570
                $propertyName,
571
                $resourceType,
572
                $relativeUri,
573
                $content
574
            );
575
576
            $odataProperty->typeName = $actualType->getFullName();
577
            $odataProperty->value = $content;
578
        }
579
580
        $odataPropertyContent->properties[] = $odataProperty;
581
    }
582
583
    /**
584
     * Write value of a bag instance.
585
     *
586
     * @param array/NULL           &$BagValue             Bag value to write
587
     * @param string               $propertyName          Property name of the bag
588
     * @param ResourceType         &$resourceType         Type describing the
589
     *                                                    bag value
590
     * @param string               $relativeUri           Relative Url to the bag
591
     * @param ODataPropertyContent &$odataPropertyContent On return, this object
592
     *                                                    will hold bag value which
593
     *                                                    can be used by writers
594
     */
595
    private function writeBagValue(
596
        &$BagValue,
597
        $propertyName,
598
        ResourceType &$resourceType,
599
        $relativeUri,
600
        ODataPropertyContent &$odataPropertyContent
601
    ) {
602
        assert(null == $BagValue || is_array($BagValue), 'Bag parameter must be null or array');
603
        $bagItemResourceTypeKind = $resourceType->getResourceTypeKind();
604
        assert(
605
            ResourceTypeKind::PRIMITIVE == $bagItemResourceTypeKind
606
            || ResourceTypeKind::COMPLEX == $bagItemResourceTypeKind,
607
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
608
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
609
        );
610
611
        $odataProperty = new ODataProperty();
612
        $odataProperty->name = $propertyName;
613
        $odataProperty->typeName = 'Collection(' . $resourceType->getFullName() . ')';
614
615
        if (is_null($BagValue) || (is_array($BagValue) && empty($BagValue))) {
616
            $odataProperty->value = null;
617
        } else {
618
            $odataBagContent = new ODataBagContent();
619
            foreach ($BagValue as $itemValue) {
620
                // strip out null elements
621
                if (isset($itemValue)) {
622
                    if (ResourceTypeKind::PRIMITIVE == $bagItemResourceTypeKind) {
623
                        $odataBagContent->propertyContents[] = $this->primitiveToString($resourceType, $itemValue);
624
                    } elseif (ResourceTypeKind::COMPLEX == $bagItemResourceTypeKind) {
625
                        $complexContent = new ODataPropertyContent();
626
                        $this->complexObjectToContent(
627
                            $itemValue,
628
                            $propertyName,
629
                            $resourceType,
630
                            $relativeUri,
631
                            $complexContent
632
                        );
633
                        //TODO add type in case of base type
634
                        $odataBagContent->propertyContents[] = $complexContent;
635
                    }
636
                }
637
            }
638
639
            $odataProperty->value = $odataBagContent;
640
        }
641
642
        $odataPropertyContent->properties[] = $odataProperty;
643
    }
644
645
    /**
646
     * Write media resource metadata (for MLE and Named Streams).
647
     *
648
     * @param mixed        $entryObject   The entry instance being serialized
649
     * @param ResourceType &$resourceType Resource type of the entry instance
650
     * @param string       $title         Title for the current
651
     *                                    current entry instance
652
     * @param string       $relativeUri   Relative uri for the
653
     *                                    current entry instance
654
     * @param ODataEntry   &$odataEntry   OData entry to write to
655
     */
656
    private function writeMediaResourceMetadata(
657
        $entryObject,
658
        ResourceType &$resourceType,
659
        $title,
660
        $relativeUri,
661
        ODataEntry &$odataEntry
662
    ) {
663
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
664
        assert(null != $streamProviderWrapper, "Retrieved stream provider must not be null");
665
        $context = $this->getService()->getOperationContext();
666
        if ($resourceType->isMediaLinkEntry()) {
667
            $odataEntry->isMediaLinkEntry = true;
668
            $eTag = $streamProviderWrapper->getStreamETag2($entryObject, null, $context);
1 ignored issue
show
Bug introduced by
The method getStreamETag2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getStreamETag()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
669
            $readStreamUri = $streamProviderWrapper->getReadStreamUri2($entryObject, null, $context, $relativeUri);
0 ignored issues
show
Bug introduced by
The method getReadStreamUri2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getReadStream()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
670
            $mediaContentType = $streamProviderWrapper->getStreamContentType2($entryObject, null, $context);
0 ignored issues
show
Bug introduced by
The method getStreamContentType2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getStreamContentType()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
671
            $mediaLink = new ODataMediaLink(
672
                $title,
673
                $streamProviderWrapper->getDefaultStreamEditMediaUri(
674
                    $relativeUri,
675
                    $resourceType,
0 ignored issues
show
Documentation introduced by
$resourceType is of type object<POData\Providers\Metadata\ResourceType>, but the function expects a null|object<POData\Provi...ata\ResourceStreamInfo>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
676
                    null,
0 ignored issues
show
Unused Code introduced by
The call to StreamProviderWrapper::g...ultStreamEditMediaUri() has too many arguments starting with null.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
677
                    $context
678
                ),
679
                $readStreamUri,
680
                $mediaContentType,
681
                $eTag
682
            );
683
684
            $odataEntry->mediaLink = $mediaLink;
685
        }
686
687
        if ($resourceType->hasNamedStream()) {
688
            foreach ($resourceType->getAllNamedStreams() as $title => $resourceStreamInfo) {
689
                $eTag = $streamProviderWrapper->getStreamETag2(
1 ignored issue
show
Bug introduced by
The method getStreamETag2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getStreamETag()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
690
                    $entryObject,
691
                    $resourceStreamInfo,
692
                    $context
693
                );
694
                $readStreamUri = $streamProviderWrapper->getReadStreamUri2(
1 ignored issue
show
Bug introduced by
The method getReadStreamUri2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getReadStream()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
695
                    $entryObject,
696
                    $resourceStreamInfo,
697
                    $context,
698
                    $relativeUri
699
                );
700
                $mediaContentType = $streamProviderWrapper->getStreamContentType2(
1 ignored issue
show
Bug introduced by
The method getStreamContentType2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getStreamContentType()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
701
                    $entryObject,
702
                    $resourceStreamInfo,
703
                    $context
704
                );
705
                $mediaLink = new ODataMediaLink(
706
                    $title,
707
                    $streamProviderWrapper->getReadStreamUri2(
0 ignored issues
show
Bug introduced by
The method getReadStreamUri2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getReadStream()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
708
                        $entryObject,
709
                        $resourceStreamInfo,
710
                        $context,
711
                        $relativeUri
712
                    ),
713
                    $readStreamUri,
714
                    $mediaContentType,
715
                    $eTag
716
                );
717
718
                $odataEntry->mediaLinks[] = $mediaLink;
719
            }
720
        }
721
    }
722
723
    /**
724
     * Convert the given primitive value to string.
725
     * Note: This method will not handle null primitive value.
726
     *
727
     * @param ResourceType &$primitiveResourceType Type of the primitive property
728
     *                                             whose value need to be converted
729
     * @param mixed        $primitiveValue         Primitive value to convert
730
     *
731
     * @return string
732
     */
733
    private function primitiveToString(
734
        ResourceType &$primitiveResourceType,
735
        $primitiveValue
736
    ) {
737
        $type = $primitiveResourceType->getInstanceType();
738
        if ($type instanceof Boolean) {
739
            $stringValue = ($primitiveValue === true) ? 'true' : 'false';
740
        } elseif ($type instanceof Binary) {
741
            $stringValue = base64_encode($primitiveValue);
742
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
743
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
744
        } elseif ($type instanceof StringType) {
745
            $stringValue = utf8_encode($primitiveValue);
746
        } else {
747
            $stringValue = strval($primitiveValue);
748
        }
749
750
        return $stringValue;
751
    }
752
753
    /**
754
     * Write value of a complex object.
755
     * Note: This method will not handle null complex value.
756
     *
757
     * @param mixed                &$complexValue         Complex object to write
758
     * @param string               $propertyName          Name of the
759
     *                                                    complex property
760
     *                                                    whose value
761
     *                                                    need to be written
762
     * @param ResourceType         &$resourceType         Expected type of the
763
     *                                                    property
764
     * @param string               $relativeUri           Relative uri for the
765
     *                                                    complex type element
766
     * @param ODataPropertyContent &$odataPropertyContent Content to write to
767
     *
768
     * @return ResourceType The actual type of the complex object
769
     */
770
    private function complexObjectToContent(
771
        &$complexValue,
772
        $propertyName,
773
        ResourceType &$resourceType,
774
        $relativeUri,
775
        ODataPropertyContent &$odataPropertyContent
776
    ) {
777
        $count = count($this->complexTypeInstanceCollection);
778
        for ($i = 0; $i < $count; ++$i) {
779
            if ($this->complexTypeInstanceCollection[$i] === $complexValue) {
780
                throw new InvalidOperationException(
781
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
782
                );
783
            }
784
        }
785
786
        $this->complexTypeInstanceCollection[$count] = &$complexValue;
787
788
        //TODO function to resolve actual type from $resourceType
789
        $actualType = $resourceType;
790
        $odataEntry = null;
791
        $this->writeObjectProperties(
792
            $complexValue,
793
            $actualType,
794
            null,
795
            $relativeUri,
796
            $odataEntry,
797
            $odataPropertyContent
798
        );
799
        unset($this->complexTypeInstanceCollection[$count]);
800
801
        return $actualType;
802
    }
803
804
    /**
805
     * @param object $customObject
806
     * @param ResourceType $resourceType
807
     * @param string $relativeUri
808
     * @param ODataPropertyContent $odataPropertyContent
809
     * @param ResourceTypeKind $resourceTypeKind
810
     * @param $navigationProperties
811
     *
812
     * @throws ODataException
813
     *
814
     * @return array
815
     */
816
    private function writeObjectPropertiesUnexpanded(
817
        $customObject,
818
        ResourceType &$resourceType,
819
        $relativeUri,
820
        ODataPropertyContent &$odataPropertyContent,
821
        $resourceTypeKind,
822
        $navigationProperties
823
    ) {
824
        assert(is_object($customObject), 'Supplied $customObject must be an object');
825
        //This is the code path to handle properties of Complex type
826
        //or Entry without projection (i.e. no expansion or selection)
827
        if (ResourceTypeKind::ENTITY == $resourceTypeKind) {
828
            // If custom object is an entry then it can contain navigation
829
            // properties which are invisible (because the corresponding
830
            // resource set is invisible).
831
            // IDSMP::getResourceProperties will give collection of properties
832
            // which are visible.
833
            $currentResourceSetWrapper1 = $this->getCurrentResourceSetWrapper();
834
            $resourceProperties = $this->getService()
835
                ->getProvidersWrapper()
836
                ->getResourceProperties(
837
                    $currentResourceSetWrapper1,
838
                    $resourceType
839
                );
840
        } else {
841
            $resourceProperties = $resourceType->getAllProperties();
842
        }
843
844
        $nonPrimitiveProperties = [];
845
        //First write out primitive types
846
        foreach ($resourceProperties as $name => $resourceProperty) {
847
            $resourceKind = $resourceProperty->getKind();
848
            if (ObjectModelSerializer::isMatchPrimitive($resourceKind)) {
849
                $odataProperty = new ODataProperty();
850
                $primitiveValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
851
                $this->writePrimitiveValue($primitiveValue, $odataProperty, $resourceProperty);
852
                $odataPropertyContent->properties[] = $odataProperty;
853
            } else {
854
                $nonPrimitiveProperties[] = $resourceProperty;
855
            }
856
        }
857
858
        //Write out bag and complex type
859
        $i = 0;
860
        foreach ($nonPrimitiveProperties as $resourceProperty) {
861
            if ($resourceProperty->isKindOf(ResourcePropertyKind::BAG)) {
862
                //Handle Bag Property (Bag of Primitive or complex)
863
                $propertyValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
864
                $resourceType2 = $resourceProperty->getResourceType();
865
                $this->writeBagValue(
866
                    $propertyValue,
867
                    $resourceProperty->getName(),
868
                    $resourceType2,
869
                    $relativeUri . '/' . $resourceProperty->getName(),
870
                    $odataPropertyContent
871
                );
872
            } else {
873
                $resourceKind = $resourceProperty->getKind();
874
                if (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
875
                    $propertyValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
876
                    $resourceType1 = $resourceProperty->getResourceType();
877
                    $this->writeComplexValue(
878
                        $propertyValue,
879
                        $resourceProperty->getName(),
880
                        $resourceType1,
881
                        $relativeUri . '/' . $resourceProperty->getName(),
882
                        $odataPropertyContent
883
                    );
884
                } else {
885
                    assert(
886
                        (ResourcePropertyKind::RESOURCE_REFERENCE == $resourceKind)
887
                        || (ResourcePropertyKind::RESOURCESET_REFERENCE == $resourceKind),
888
                        '($resourceKind != ResourcePropertyKind::RESOURCE_REFERENCE)'
889
                        .'&& ($resourceKind != ResourcePropertyKind::RESOURCESET_REFERENCE)'
890
                    );
891
892
                    $navigationProperties[$i] = new ODataNavigationPropertyInfo(
893
                        $resourceProperty,
894
                        $this->shouldExpandSegment($resourceProperty->getName())
895
                    );
896
                    if ($navigationProperties[$i]->expanded) {
897
                        $navigationProperties[$i]->value = $this->getPropertyValue(
898
                            $customObject,
899
                            $resourceType,
900
                            $resourceProperty
901
                        );
902
                    }
903
904
                    ++$i;
905
                }
906
            }
907
        }
908
909
        return [$odataPropertyContent, $navigationProperties];
910
    }
911
912
    public static function isMatchPrimitive($resourceKind)
913
    {
914
        if (16 > $resourceKind) {
915
            return false;
916
        }
917
        if (28 < $resourceKind) {
918
            return false;
919
        }
920
        return 0 == ($resourceKind % 4);
921
    }
922
923
924
    /**
925
     * @param object $customObject
926
     * @param ResourceType $resourceType
927
     * @param string $relativeUri
928
     * @param ODataPropertyContent $odataPropertyContent
929
     * @param \POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode[] $projectionNodes
930
     * @param $navigationProperties
931
     *
932
     * @throws ODataException
933
     *
934
     * @return array
935
     */
936
    private function writeObjectPropertiesExpanded(
937
        $customObject,
938
        ResourceType &$resourceType,
939
        $relativeUri,
940
        ODataPropertyContent &$odataPropertyContent,
941
        $projectionNodes,
942
        $navigationProperties
943
    ) {
944
        assert(is_object($customObject), 'Supplied $customObject must be an object');
945
        $i = 0;
946
        foreach ($projectionNodes as $projectionNode) {
947
            $propertyName = $projectionNode->getPropertyName();
948
            $resourceProperty = $resourceType->resolveProperty($propertyName);
949
            assert(!is_null($resourceProperty), 'is_null($resourceProperty)');
950
951
            if (ResourceTypeKind::ENTITY == $resourceProperty->getTypeKind()) {
952
                $currentResourceSetWrapper2 = $this->getCurrentResourceSetWrapper();
953
                $resourceProperties = $this->getService()
954
                    ->getProvidersWrapper()
955
                    ->getResourceProperties(
956
                        $currentResourceSetWrapper2,
957
                        $resourceType
958
                    );
959
                //Check for the visibility of this navigation property
960
                if (array_key_exists($resourceProperty->getName(), $resourceProperties)) {
961
                    $navigationProperties[$i] = new ODataNavigationPropertyInfo(
962
                        $resourceProperty,
963
                        $this->shouldExpandSegment($propertyName)
964
                    );
965
                    if ($navigationProperties[$i]->expanded) {
966
                        $navigationProperties[$i]->value = $this->getPropertyValue(
967
                            $customObject,
968
                            $resourceType,
969
                            $resourceProperty
970
                        );
971
                    }
972
973
                    ++$i;
974
                    continue;
975
                }
976
            }
977
978
            //Primitive, complex or bag property
979
            $propertyValue = $this->getPropertyValue($customObject, $resourceType, $resourceProperty);
980
            $propertyTypeKind = $resourceProperty->getKind();
981
            $propertyResourceType = $resourceProperty->getResourceType();
982
            assert(!is_null($propertyResourceType), 'is_null($propertyResourceType)');
983
            if ($resourceProperty->isKindOf(ResourcePropertyKind::BAG)) {
984
                $bagResourceType = $resourceProperty->getResourceType();
985
                $this->writeBagValue(
986
                    $propertyValue,
987
                    $propertyName,
988
                    $bagResourceType,
989
                    $relativeUri . '/' . $propertyName,
990
                    $odataPropertyContent
991
                );
992
            } elseif ($resourceProperty->isKindOf(ResourcePropertyKind::PRIMITIVE)) {
993
                $odataProperty = new ODataProperty();
994
                $this->writePrimitiveValue($propertyValue, $odataProperty, $resourceProperty);
995
                $odataPropertyContent->properties[] = $odataProperty;
996
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $propertyTypeKind) {
997
                $complexResourceType = $resourceProperty->getResourceType();
998
                $this->writeComplexValue(
999
                    $propertyValue,
1000
                    $propertyName,
1001
                    $complexResourceType,
1002
                    $relativeUri . '/' . $propertyName,
1003
                    $odataPropertyContent
1004
                );
1005
            } else {
1006
                //unexpected
1007
                assert(false, '$propertyTypeKind != Primitive or Bag or ComplexType');
1008
            }
1009
        }
1010
1011
        return [$odataPropertyContent, $navigationProperties];
1012
    }
1013
}
1014