Test Failed
Pull Request — master (#81)
by Alex
02:55
created

IronicSerialiser::writeBagValue()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 25
rs 5.3846
cc 8
eloc 18
nc 6
nop 2
1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Serialisers;
4
5
use POData\Common\Messages;
6
use POData\Common\ODataConstants;
7
use POData\Common\ODataException;
8
use POData\IService;
9
use POData\ObjectModel\IObjectSerialiser;
10
use POData\ObjectModel\ODataBagContent;
11
use POData\ObjectModel\ODataEntry;
12
use POData\ObjectModel\ODataFeed;
13
use POData\ObjectModel\ODataLink;
14
use POData\ObjectModel\ODataMediaLink;
15
use POData\ObjectModel\ODataNavigationPropertyInfo;
16
use POData\ObjectModel\ODataProperty;
17
use POData\ObjectModel\ODataPropertyContent;
18
use POData\ObjectModel\ODataURL;
19
use POData\ObjectModel\ODataURLCollection;
20
use POData\Providers\Metadata\ResourceEntityType;
21
use POData\Providers\Metadata\ResourceProperty;
22
use POData\Providers\Metadata\ResourcePropertyKind;
23
use POData\Providers\Metadata\ResourceSet;
24
use POData\Providers\Metadata\ResourceSetWrapper;
25
use POData\Providers\Metadata\ResourceType;
26
use POData\Providers\Metadata\ResourceTypeKind;
27
use POData\Providers\Metadata\Type\Binary;
28
use POData\Providers\Metadata\Type\Boolean;
29
use POData\Providers\Metadata\Type\DateTime;
30
use POData\Providers\Metadata\Type\IType;
31
use POData\Providers\Metadata\Type\StringType;
32
use POData\Providers\Query\QueryResult;
33
use POData\Providers\Query\QueryType;
34
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
35
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode;
36
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
37
use POData\UriProcessor\RequestDescription;
38
use POData\UriProcessor\SegmentStack;
39
40
class IronicSerialiser implements IObjectSerialiser
41
{
42
    /**
43
     * The service implementation.
44
     *
45
     * @var IService
46
     */
47
    protected $service;
48
49
    /**
50
     * Request description instance describes OData request the
51
     * the client has submitted and result of the request.
52
     *
53
     * @var RequestDescription
54
     */
55
    protected $request;
56
57
    /**
58
     * Collection of complex type instances used for cycle detection.
59
     *
60
     * @var array
61
     */
62
    protected $complexTypeInstanceCollection;
63
64
    /**
65
     * Absolute service Uri.
66
     *
67
     * @var string
68
     */
69
    protected $absoluteServiceUri;
70
71
    /**
72
     * Absolute service Uri with slash.
73
     *
74
     * @var string
75
     */
76
    protected $absoluteServiceUriWithSlash;
77
78
    /**
79
     * Holds reference to segment stack being processed.
80
     *
81
     * @var SegmentStack
82
     */
83
    protected $stack;
84
85
    /**
86
     * Lightweight stack tracking for recursive descent fill
87
     */
88
    private $lightStack = [];
89
90
    private $modelSerialiser;
91
92
    /**
93
     * @param IService           $service Reference to the data service instance
94
     * @param RequestDescription $request Type instance describing the client submitted request
95
     */
96
    public function __construct(IService $service, RequestDescription $request = null)
97
    {
98
        $this->service = $service;
99
        $this->request = $request;
100
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
101
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
102
        $this->stack = new SegmentStack($request);
103
        $this->complexTypeInstanceCollection = [];
104
        $this->modelSerialiser = new ModelSerialiser();
105
    }
106
107
    /**
108
     * Write a top level entry resource.
109
     *
110
     * @param mixed $entryObject Reference to the entry object to be written
111
     *
112
     * @return ODataEntry
113
     */
114
    public function writeTopLevelElement(QueryResult $entryObject)
115
    {
116
        if (!isset($entryObject->results)) {
117
            array_pop($this->lightStack);
118
            return null;
119
        }
120
121
        $this->loadStackIfEmpty();
122
123
        $stackCount = count($this->lightStack);
124
        $topOfStack = $this->lightStack[$stackCount-1];
125
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack[0]);
126
        $rawProp = $resourceType->getAllProperties();
127
        $relProp = [];
128
        $nonRelProp = [];
129
        foreach ($rawProp as $prop) {
130
            if ($prop->getResourceType() instanceof ResourceEntityType) {
131
                $relProp[] = $prop;
132
            } else {
133
                $nonRelProp[$prop->getName()] = $prop;
134
            }
135
        }
136
137
        $resourceSet = $resourceType->getCustomState();
138
        assert($resourceSet instanceof ResourceSet);
139
        $title = $resourceType->getName();
140
        $type = $resourceType->getFullName();
141
142
        $relativeUri = $this->getEntryInstanceKey(
143
            $entryObject->results,
144
            $resourceType,
145
            $resourceSet->getName()
146
        );
147
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
148
149
        list($mediaLink, $mediaLinks) = $this->writeMediaData($entryObject->results, $type, $relativeUri, $resourceType);
150
151
        $propertyContent = $this->writePrimitiveProperties($entryObject->results, $nonRelProp);
152
153
        $links = [];
154
        foreach ($relProp as $prop) {
155
            $nuLink = new ODataLink();
156
            $propKind = $prop->getKind();
157
158
            assert(
159
                ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
160
                || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind,
161
                '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
162
                .' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE'
163
            );
164
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
165
            $propType = 'application/atom+xml;type='.$propTail;
166
            $propName = $prop->getName();
167
            $nuLink->title = $propName;
168
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
169
            $nuLink->url = $relativeUri . '/' . $propName;
170
            $nuLink->type = $propType;
171
172
            $navProp = new ODataNavigationPropertyInfo($prop, $this->shouldExpandSegment($propName));
173
            if ($navProp->expanded) {
174
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
175
            }
176
177
            $links[] = $nuLink;
178
        }
179
180
        $odata = new ODataEntry();
181
        $odata->resourceSetName = $resourceSet->getName();
182
        $odata->id = $absoluteUri;
183
        $odata->title = $title;
184
        $odata->type = $type;
185
        $odata->propertyContent = $propertyContent;
186
        $odata->isMediaLinkEntry = $resourceType->isMediaLinkEntry();
187
        $odata->editLink = $relativeUri;
188
        $odata->mediaLink = $mediaLink;
189
        $odata->mediaLinks = $mediaLinks;
190
        $odata->links = $links;
191
192
        $newCount = count($this->lightStack);
193
        assert($newCount == $stackCount, "Should have $stackCount elements in stack, have $newCount elements");
194
        array_pop($this->lightStack);
195
        return $odata;
196
    }
197
198
    /**
199
     * Write top level feed element.
200
     *
201
     * @param array &$entryObjects Array of entry resources to be written
202
     *
203
     * @return ODataFeed
204
     */
205
    public function writeTopLevelElements(QueryResult &$entryObjects)
206
    {
207
        assert(is_array($entryObjects->results), '!is_array($entryObjects->results)');
208
209
        $this->loadStackIfEmpty();
210
        $setName = $this->getRequest()->getTargetResourceSetWrapper()->getName();
211
212
        $title = $this->getRequest()->getContainerName();
213
        $relativeUri = $this->getRequest()->getIdentifier();
214
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
215
216
        $selfLink = new ODataLink();
217
        $selfLink->name = 'self';
218
        $selfLink->title = $relativeUri;
219
        $selfLink->url = $relativeUri;
220
221
        $odata = new ODataFeed();
222
        $odata->title = $title;
223
        $odata->id = $absoluteUri;
224
        $odata->selfLink = $selfLink;
225
226
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
227
            $odata->rowCount = $this->getRequest()->getCountValue();
228
        }
229
        foreach ($entryObjects->results as $entry) {
230
            if (!$entry instanceof QueryResult) {
231
                $query = new QueryResult();
232
                $query->results = $entry;
233
            } else {
234
                $query = $entry;
235
            }
236
            $odata->entries[] = $this->writeTopLevelElement($query);
237
        }
238
239
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
240
        $requestTop = $this->getRequest()->getTopOptionCount();
241
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
242
        $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...
243
244
        if (true === $entryObjects->hasMore && $requestTop > $pageSize) {
0 ignored issues
show
Bug introduced by
The property hasMore does not seem to exist in POData\Providers\Query\QueryResult.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
245
            $stackSegment = $setName;
246
            $lastObject = end($entryObjects->results);
247
            $segment = $this->getNextLinkUri($lastObject, $absoluteUri);
248
            $nextLink = new ODataLink();
249
            $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
250
            $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
251
            $odata->nextPageLink = $nextLink;
252
        }
253
254
        return $odata;
255
    }
256
257
    /**
258
     * Write top level url element.
259
     *
260
     * @param mixed $entryObject The entry resource whose url to be written
261
     *
262
     * @return ODataURL
263
     */
264
    public function writeUrlElement(QueryResult $entryObject)
265
    {
266
        $url = new ODataURL();
267
        if (!is_null($entryObject->results)) {
268
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
269
            $relativeUri = $this->getEntryInstanceKey(
270
                $entryObject->results,
271
                $currentResourceType,
272
                $this->getCurrentResourceSetWrapper()->getName()
273
            );
274
275
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
276
        }
277
278
        return $url;
279
    }
280
281
    /**
282
     * Write top level url collection.
283
     *
284
     * @param array $entryObjects Array of entry resources
285
     *                            whose url to be written
286
     *
287
     * @return ODataURLCollection
288
     */
289
    public function writeUrlElements(QueryResult $entryObjects)
290
    {
291
        $urls = new ODataURLCollection();
292
        if (!empty($entryObjects->results)) {
293
            $i = 0;
294
            foreach ($entryObjects->results as $entryObject) {
295
                $urls->urls[$i] = $this->writeUrlElement($entryObject);
296
                ++$i;
297
            }
298
299
            if ($i > 0 && true === $entryObjects->hasMore) {
0 ignored issues
show
Bug introduced by
The property hasMore does not seem to exist in POData\Providers\Query\QueryResult.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
300
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
301
                $lastObject = end($entryObjects->results);
302
                $segment = $this->getNextLinkUri($lastObject, $this->getRequest()->getRequestUrl()->getUrlAsString());
303
                $nextLink = new ODataLink();
304
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
305
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
306
                $urls->nextPageLink = $nextLink;
307
            }
308
        }
309
310
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
311
            $urls->count = $this->getRequest()->getCountValue();
312
        }
313
314
        return $urls;
315
    }
316
317
    /**
318
     * Write top level complex resource.
319
     *
320
     * @param mixed &$complexValue The complex object to be
321
     *                                    written
322
     * @param string $propertyName The name of the
323
     *                                    complex property
324
     * @param ResourceType &$resourceType Describes the type of
325
     *                                    complex object
326
     *
327
     * @return ODataPropertyContent
328
     */
329
    public function writeTopLevelComplexObject(QueryResult &$complexValue, $propertyName, ResourceType &$resourceType)
330
    {
331
        $result = $complexValue->results;
332
333
        $propertyContent = new ODataPropertyContent();
334
        $odataProperty = new ODataProperty();
335
        $odataProperty->name = $propertyName;
336
        $odataProperty->typeName = $resourceType->getFullName();
337
        if (null != $result) {
338
            $internalContent = $this->writeComplexValue($resourceType, $result);
339
340
            $odataProperty->value = $internalContent;
341
        }
342
343
        $propertyContent->properties[] = $odataProperty;
344
345
        return $propertyContent;
346
    }
347
348
    /**
349
     * Write top level bag resource.
350
     *
351
     * @param mixed &$BagValue The bag object to be
352
     *                                    written
353
     * @param string $propertyName The name of the
354
     *                                    bag property
355
     * @param ResourceType &$resourceType Describes the type of
356
     *                                    bag object
357
     * @return ODataPropertyContent
358
     */
359
    public function writeTopLevelBagObject(QueryResult &$BagValue, $propertyName, ResourceType &$resourceType)
360
    {
361
        $result = $BagValue->results;
362
363
        $propertyContent = new ODataPropertyContent();
364
        $odataProperty = new ODataProperty();
365
        $odataProperty->name = $propertyName;
366
        $odataProperty->typeName = 'Collection('.$resourceType->getFullName().')';
367
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
368
369
        $propertyContent->properties[] = $odataProperty;
370
        return $propertyContent;
371
    }
372
373
    /**
374
     * Write top level primitive value.
375
     *
376
     * @param mixed &$primitiveValue The primitve value to be
377
     *                                            written
378
     * @param ResourceProperty &$resourceProperty Resource property
379
     *                                            describing the
380
     *                                            primitive property
381
     *                                            to be written
382
     * @return ODataPropertyContent
383
     */
384
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
385
    {
386
        assert(null != $resourceProperty, "Resource property must not be null");
387
        $propertyContent = new ODataPropertyContent();
388
389
        $odataProperty = new ODataProperty();
390
        $odataProperty->name = $resourceProperty->getName();
391
        $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...
392
        if (null == $primitiveValue->results) {
393
            $odataProperty->value = null;
394
        } else {
395
            $rType = $resourceProperty->getResourceType()->getInstanceType();
396
            $odataProperty->value = $this->primitiveToString($rType, $primitiveValue->results);
0 ignored issues
show
Bug introduced by
It seems like $rType defined by $resourceProperty->getRe...pe()->getInstanceType() on line 395 can also be of type object<ReflectionClass> or string; however, AlgoWeb\PODataLaravel\Se...er::primitiveToString() does only seem to accept object<POData\Providers\Metadata\Type\IType>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
397
        }
398
399
        $propertyContent->properties[] = $odataProperty;
400
401
        return $propertyContent;
402
    }
403
404
    /**
405
     * Gets reference to the request submitted by client.
406
     *
407
     * @return RequestDescription
408
     */
409
    public function getRequest()
410
    {
411
        assert(null != $this->request, 'Request not yet set');
412
413
        return $this->request;
414
    }
415
416
    /**
417
     * Sets reference to the request submitted by client.
418
     *
419
     * @param RequestDescription $request
420
     */
421
    public function setRequest(RequestDescription $request)
422
    {
423
        $this->request = $request;
424
        $this->stack->setRequest($request);
425
    }
426
427
    /**
428
     * Gets the data service instance.
429
     *
430
     * @return IService
431
     */
432
    public function getService()
433
    {
434
        return $this->service;
435
    }
436
437
    /**
438
     * Gets the segment stack instance.
439
     *
440
     * @return SegmentStack
441
     */
442
    public function getStack()
443
    {
444
        return $this->stack;
445
    }
446
447
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
448
    {
449
        $typeName = $resourceType->getName();
450
        $keyProperties = $resourceType->getKeyProperties();
451
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
452
        $keyString = $containerName . '(';
453
        $comma = null;
454
        foreach ($keyProperties as $keyName => $resourceProperty) {
455
            $keyType = $resourceProperty->getInstanceType();
456
            assert($keyType instanceof IType, '$keyType not instanceof IType');
457
            $keyName = $resourceProperty->getName();
458
            $keyValue = $entityInstance->$keyName;
459
            if (!isset($keyValue)) {
460
                throw ODataException::createInternalServerError(
461
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
462
                );
463
            }
464
465
            $keyValue = $keyType->convertToOData($keyValue);
466
            $keyString .= $comma . $keyName . '=' . $keyValue;
467
            $comma = ',';
468
        }
469
470
        $keyString .= ')';
471
472
        return $keyString;
473
    }
474
475
    /**
476
     * @param $entryObject
477
     * @param $type
478
     * @param $relativeUri
479
     * @param $resourceType
480
     * @return array
481
     */
482
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
483
    {
484
        $context = $this->getService()->getOperationContext();
485
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
486
        assert(null != $streamProviderWrapper, "Retrieved stream provider must not be null");
487
488
        $mediaLink = null;
489
        if ($resourceType->isMediaLinkEntry()) {
490
            $eTag = $streamProviderWrapper->getStreamETag2($entryObject, null, $context);
0 ignored issues
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...
491
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag);
492
        }
493
        $mediaLinks = [];
494
        if ($resourceType->hasNamedStream()) {
495
            $namedStreams = $resourceType->getAllNamedStreams();
496
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
497
                $readUri = $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...
498
                    $entryObject,
499
                    $resourceStreamInfo,
500
                    $context,
501
                    $relativeUri
502
                );
503
                $mediaContentType = $streamProviderWrapper->getStreamContentType2(
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...
504
                    $entryObject,
505
                    $resourceStreamInfo,
506
                    $context
507
                );
508
                $eTag = $streamProviderWrapper->getStreamETag2(
0 ignored issues
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...
509
                    $entryObject,
510
                    $resourceStreamInfo,
511
                    $context
512
                );
513
514
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
515
                $mediaLinks[] = $nuLink;
516
            }
517
        }
518
        return [$mediaLink, $mediaLinks];
519
    }
520
521
    /**
522
     * Gets collection of projection nodes under the current node.
523
     *
524
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes
525
     *                                                        describing projections for the current segment, If this method returns
526
     *                                                        null it means no projections are to be applied and the entire resource
527
     *                                                        for the current segment should be serialized, If it returns non-null
528
     *                                                        only the properties described by the returned projection segments should
529
     *                                                        be serialized
530
     */
531
    protected function getProjectionNodes()
532
    {
533
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
534
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
535
            return null;
536
        }
537
538
        return $expandedProjectionNode->getChildNodes();
539
    }
540
541
    /**
542
     * Find a 'ExpandedProjectionNode' instance in the projection tree
543
     * which describes the current segment.
544
     *
545
     * @return ExpandedProjectionNode|null
546
     */
547
    protected function getCurrentExpandedProjectionNode()
548
    {
549
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
550
        if (is_null($expandedProjectionNode)) {
551
            return null;
552
        } else {
553
            $segmentNames = $this->getStack()->getSegmentNames();
554
            $depth = count($segmentNames);
555
            // $depth == 1 means serialization of root entry
556
            //(the resource identified by resource path) is going on,
557
            //so control won't get into the below for loop.
558
            //we will directly return the root node,
559
            //which is 'ExpandedProjectionNode'
560
            // for resource identified by resource path.
561
            if (0 != $depth) {
562
                for ($i = 1; $i < $depth; ++$i) {
563
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]);
564
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
565
                    assert(
566
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
567
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
568
                    );
569
                }
570
            }
571
        }
572
573
        return $expandedProjectionNode;
574
    }
575
576
    /**
577
     * Check whether to expand a navigation property or not.
578
     *
579
     * @param string $navigationPropertyName Name of naviagtion property in question
580
     *
581
     * @return bool True if the given navigation should be
582
     *              explanded otherwise false
583
     */
584
    protected function shouldExpandSegment($navigationPropertyName)
585
    {
586
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
587
        if (is_null($expandedProjectionNode)) {
588
            return false;
589
        }
590
591
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
592
593
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
594
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
595
    }
596
597
    /**
598
     * Wheter next link is needed for the current resource set (feed)
599
     * being serialized.
600
     *
601
     * @param int $resultSetCount Number of entries in the current
602
     *                            resource set
603
     *
604
     * @return bool true if the feed must have a next page link
605
     */
606
    protected function needNextPageLink($resultSetCount)
607
    {
608
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
609
        $recursionLevel = count($this->getStack()->getSegmentNames());
610
        $pageSize = $currentResourceSet->getResourceSetPageSize();
611
612
        if (1 == $recursionLevel) {
613
            //presence of $top option affect next link for root container
614
            $topValueCount = $this->getRequest()->getTopOptionCount();
615
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
616
                return false;
617
            }
618
        }
619
        return $resultSetCount == $pageSize;
620
    }
621
622
    /**
623
     * Resource set wrapper for the resource being serialized.
624
     *
625
     * @return ResourceSetWrapper
626
     */
627
    protected function getCurrentResourceSetWrapper()
628
    {
629
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
630
        $count = count($segmentWrappers);
631
632
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
633
    }
634
635
    /**
636
     * Get next page link from the given entity instance.
637
     *
638
     * @param mixed  &$lastObject Last object serialized to be
639
     *                            used for generating $skiptoken
640
     * @param string $absoluteUri Absolute response URI
641
     *
642
     * @return string for the link for next page
643
     */
644
    protected function getNextLinkUri(&$lastObject, $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...
645
    {
646
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
647
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
648
        assert(null != $internalOrderByInfo);
649
        assert(is_object($internalOrderByInfo));
650
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
651
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
652
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
653
654
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
655
        assert(!is_null($skipToken), '!is_null($skipToken)');
656
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
657
        $skipToken = '?'.$queryParameterString.$token.$skipToken;
658
659
        return $skipToken;
660
    }
661
662
    /**
663
     * Builds the string corresponding to query parameters for top level results
664
     * (result set identified by the resource path) to be put in next page link.
665
     *
666
     * @return string|null string representing the query parameters in the URI
667
     *                     query parameter format, NULL if there
668
     *                     is no query parameters
669
     *                     required for the next link of top level result set
670
     */
671
    protected function getNextPageLinkQueryParametersForRootResourceSet()
672
    {
673
        $queryParameterString = null;
674
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
675
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
676
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
677
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
678
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
679
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
680
            if (!is_null($value)) {
681
                if (!is_null($queryParameterString)) {
682
                    $queryParameterString = $queryParameterString . '&';
683
                }
684
685
                $queryParameterString .= $queryOption . '=' . $value;
686
            }
687
        }
688
689
        $topCountValue = $this->getRequest()->getTopOptionCount();
690
        if (!is_null($topCountValue)) {
691
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
692
            if (0 < $remainingCount) {
693
                if (!is_null($queryParameterString)) {
694
                    $queryParameterString .= '&';
695
                }
696
697
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
698
            }
699
        }
700
701
        if (!is_null($queryParameterString)) {
702
            $queryParameterString .= '&';
703
        }
704
705
        return $queryParameterString;
706
    }
707
708
    private function loadStackIfEmpty()
709
    {
710
        if (0 == count($this->lightStack)) {
711
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
712
            array_push($this->lightStack, [$typeName, $typeName]);
713
        }
714
    }
715
716
    /**
717
     * Convert the given primitive value to string.
718
     * Note: This method will not handle null primitive value.
719
     *
720
     * @param IType &$primitiveResourceType        Type of the primitive property
721
     *                                             whose value need to be converted
722
     * @param mixed        $primitiveValue         Primitive value to convert
723
     *
724
     * @return string
725
     */
726
    private function primitiveToString(IType &$type, $primitiveValue)
727
    {
728
        if ($type instanceof Boolean) {
729
            $stringValue = (true === $primitiveValue) ? 'true' : 'false';
730
        } elseif ($type instanceof Binary) {
731
            $stringValue = base64_encode($primitiveValue);
732
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
733
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
734
        } elseif ($type instanceof StringType) {
735
            $stringValue = utf8_encode($primitiveValue);
736
        } else {
737
            $stringValue = strval($primitiveValue);
738
        }
739
740
        return $stringValue;
741
    }
742
743
    /**
744
     * @param $entryObject
745
     * @param $nonRelProp
746
     * @return ODataPropertyContent
747
     */
748
    private function writePrimitiveProperties($entryObject, $nonRelProp)
749
    {
750
        $propertyContent = new ODataPropertyContent();
751
        $cereal = $this->modelSerialiser->bulkSerialise($entryObject);
752
        foreach ($cereal as $corn => $flake) {
753
            if (!array_key_exists($corn, $nonRelProp)) {
754
                continue;
755
            }
756
            $corn = strval($corn);
757
            $rType = $nonRelProp[$corn]->getResourceType()->getInstanceType();
758
            $subProp = new ODataProperty();
759
            $subProp->name = $corn;
760
            $subProp->value = isset($flake) ? $this->primitiveToString($rType, $flake) : null;
761
            $subProp->typeName = $nonRelProp[$corn]->getResourceType()->getFullName();
762
            $propertyContent->properties[] = $subProp;
763
        }
764
        return $propertyContent;
765
    }
766
767
    /**
768
     * @param $entryObject
769
     * @param $prop
770
     * @param $nuLink
771
     * @param $propKind
772
     * @param $propName
773
     */
774
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
775
    {
776
        $nextName = $prop->getResourceType()->getName();
777
        $nuLink->isExpanded = true;
778
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
779
        $nuLink->isCollection = $isCollection;
780
        $value = $entryObject->results->$propName;
781
        $result = new QueryResult();
782
        $result->results = $value;
783
        array_push($this->lightStack, [$nextName, $propName]);
784
        if (!$isCollection) {
785
            $expandedResult = $this->writeTopLevelElement($result);
786
        } else {
787
            $expandedResult = $this->writeTopLevelElements($result);
788
        }
789
        $nuLink->expandedResult = $expandedResult;
790
        if (!isset($nuLink->expandedResult)) {
791
            $nuLink->isCollection = null;
792
            $nuLink->isExpanded = null;
793
        } else {
794
            if (isset($nuLink->expandedResult->selfLink)) {
795
                $nuLink->expandedResult->selfLink->title = $propName;
796
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
797
                $nuLink->expandedResult->title = $propName;
798
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
799
            }
800
        }
801
    }
802
803
    /**
804
     * Gets the data service instance.
805
     *
806
     * @return IService
807
     */
808
    public function setService(IService $service)
809
    {
810
        $this->service = $service;
811
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
812
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
813
    }
814
815
    /**
816
     * @param ResourceType $resourceType
817
     * @param $result
818
     * @return ODataBagContent
819
     */
820
    protected function writeBagValue(ResourceType &$resourceType, $result)
821
    {
822
        assert(null == $result || is_array($result), 'Bag parameter must be null or array');
823
        $typeKind = $resourceType->getResourceTypeKind();
824
        assert(
825
            ResourceTypeKind::PRIMITIVE == $typeKind || ResourceTypeKind::COMPLEX == $typeKind,
826
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
827
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
828
        );
829
        if (null == $result) {
830
            return null;
831
        }
832
        $bag = new ODataBagContent();
833
        foreach ($result as $value) {
834
            if (isset($value)) {
835
                if (ResourceTypeKind::PRIMITIVE == $typeKind) {
836
                    $instance = $resourceType->getInstanceType();
837
                    $bag->propertyContents[] = $this->primitiveToString($instance, $value);
0 ignored issues
show
Bug introduced by
It seems like $instance defined by $resourceType->getInstanceType() on line 836 can also be of type object<ReflectionClass> or string; however, AlgoWeb\PODataLaravel\Se...er::primitiveToString() does only seem to accept object<POData\Providers\Metadata\Type\IType>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
838
                } elseif (ResourceTypeKind::COMPLEX == $typeKind) {
839
                    $bag->propertyContents[] = $this->writeComplexValue($resourceType, $value);
840
                }
841
            }
842
        }
843
        return $bag;
844
    }
845
846
    /**
847
     * @param ResourceType $resourceType
848
     * @param $result
849
     * @return ODataPropertyContent
850
     */
851
    protected function writeComplexValue(ResourceType &$resourceType, $result)
852
    {
853
        $internalContent = new ODataPropertyContent();
854
        $resourceProperties = $resourceType->getAllProperties();
855
        // first up, handle primitive properties
856
        foreach ($resourceProperties as $prop) {
857
            $propName = $prop->getName();
858
            $internalProperty = new ODataProperty();
859
            $internalProperty->name = $propName;
860
            $iType = $prop->getInstanceType();
861
            $internalProperty->typeName = $iType->getFullTypeName();
862
863
            $rType = $prop->getResourceType()->getInstanceType();
864
            $internalProperty->value = $this->primitiveToString($rType, $result->$propName);
865
            $internalContent->properties[] = $internalProperty;
866
        }
867
        return $internalContent;
868
    }
869
}
870