Test Failed
Push — master ( c7dd35...7c3ef1 )
by Alex
02:47
created

getNextPageLinkQueryParametersForRootResourceSet()   C

Complexity

Conditions 7
Paths 24

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 34
rs 6.7272
cc 7
eloc 21
nc 24
nop 0
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\ODataEntry;
11
use POData\ObjectModel\ODataFeed;
12
use POData\ObjectModel\ODataLink;
13
use POData\ObjectModel\ODataMediaLink;
14
use POData\ObjectModel\ODataNavigationPropertyInfo;
15
use POData\ObjectModel\ODataProperty;
16
use POData\ObjectModel\ODataPropertyContent;
17
use POData\ObjectModel\ODataURL;
18
use POData\ObjectModel\ODataURLCollection;
19
use POData\Providers\Metadata\ResourceEntityType;
20
use POData\Providers\Metadata\ResourceProperty;
21
use POData\Providers\Metadata\ResourcePropertyKind;
22
use POData\Providers\Metadata\ResourceSet;
23
use POData\Providers\Metadata\ResourceSetWrapper;
24
use POData\Providers\Metadata\ResourceType;
25
use POData\Providers\Metadata\Type\Binary;
26
use POData\Providers\Metadata\Type\Boolean;
27
use POData\Providers\Metadata\Type\DateTime;
28
use POData\Providers\Metadata\Type\IType;
29
use POData\Providers\Metadata\Type\StringType;
30
use POData\Providers\Query\QueryType;
31
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
32
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode;
33
use POData\UriProcessor\RequestDescription;
34
use POData\UriProcessor\SegmentStack;
35
36
class IronicSerialiser implements IObjectSerialiser
37
{
38
    /**
39
     * The service implementation.
40
     *
41
     * @var IService
42
     */
43
    protected $service;
44
45
    /**
46
     * Request description instance describes OData request the
47
     * the client has submitted and result of the request.
48
     *
49
     * @var RequestDescription
50
     */
51
    protected $request;
52
53
    /**
54
     * Collection of complex type instances used for cycle detection.
55
     *
56
     * @var array
57
     */
58
    protected $complexTypeInstanceCollection;
59
60
    /**
61
     * Absolute service Uri.
62
     *
63
     * @var string
64
     */
65
    protected $absoluteServiceUri;
66
67
    /**
68
     * Absolute service Uri with slash.
69
     *
70
     * @var string
71
     */
72
    protected $absoluteServiceUriWithSlash;
73
74
    /**
75
     * Holds reference to segment stack being processed.
76
     *
77
     * @var SegmentStack
78
     */
79
    protected $stack;
80
81
    /**
82
     * Lightweight stack tracking for recursive descent fill
83
     */
84
    private $lightStack = [];
85
86
    private $modelSerialiser;
87
88
    /**
89
     * @param IService           $service Reference to the data service instance
90
     * @param RequestDescription $request Type instance describing the client submitted request
91
     */
92
    public function __construct(IService $service, RequestDescription $request = null)
93
    {
94
        $this->service = $service;
95
        $this->request = $request;
96
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
97
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
98
        $this->stack = new SegmentStack($request);
99
        $this->complexTypeInstanceCollection = [];
100
        $this->modelSerialiser = new ModelSerialiser();
101
    }
102
103
    /**
104
     * Write a top level entry resource.
105
     *
106
     * @param mixed $entryObject Reference to the entry object to be written
107
     *
108
     * @return ODataEntry
109
     */
110
    public function writeTopLevelElement($entryObject)
111
    {
112
        if (!isset($entryObject)) {
113
            array_pop($this->lightStack);
114
            return null;
115
        }
116
117
        $this->loadStackIfEmpty();
118
119
        $stackCount = count($this->lightStack);
120
        $topOfStack = $this->lightStack[$stackCount-1];
121
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack[0]);
122
        $rawProp = $resourceType->getAllProperties();
123
        $relProp = [];
124
        $nonRelProp = [];
125
        foreach ($rawProp as $prop) {
126
            if ($prop->getResourceType() instanceof ResourceEntityType) {
127
                $relProp[] = $prop;
128
            } else {
129
                $nonRelProp[$prop->getName()] = $prop;
130
            }
131
        }
132
133
        $resourceSet = $resourceType->getCustomState();
134
        assert($resourceSet instanceof ResourceSet);
135
        $title = $resourceType->getName();
136
        $type = $resourceType->getFullName();
137
138
        $relativeUri = $this->getEntryInstanceKey(
139
            $entryObject,
140
            $resourceType,
141
            $resourceSet->getName()
142
        );
143
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
144
145
        list($mediaLink, $mediaLinks) = $this->writeMediaData($entryObject, $type, $relativeUri, $resourceType);
146
147
        $propertyContent = $this->writePrimitiveProperties($entryObject, $nonRelProp);
148
149
        $links = [];
150
        foreach ($relProp as $prop) {
151
            $nuLink = new ODataLink();
152
            $propKind = $prop->getKind();
153
154
            assert(
155
                ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
156
                || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind,
157
                '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
158
                .' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE'
159
            );
160
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
161
            $propType = 'application/atom+xml;type='.$propTail;
162
            $propName = $prop->getName();
163
            $nuLink->title = $propName;
164
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
165
            $nuLink->url = $relativeUri . '/' . $propName;
166
            $nuLink->type = $propType;
167
168
            $navProp = new ODataNavigationPropertyInfo($prop, $this->shouldExpandSegment($propName));
169
            if ($navProp->expanded) {
170
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
171
            }
172
173
            $links[] = $nuLink;
174
        }
175
176
        $odata = new ODataEntry();
177
        $odata->resourceSetName = $resourceSet->getName();
178
        $odata->id = $absoluteUri;
179
        $odata->title = $title;
180
        $odata->type = $type;
181
        $odata->propertyContent = $propertyContent;
182
        $odata->isMediaLinkEntry = $resourceType->isMediaLinkEntry();
183
        $odata->editLink = $relativeUri;
184
        $odata->mediaLink = $mediaLink;
185
        $odata->mediaLinks = $mediaLinks;
186
        $odata->links = $links;
187
188
        $newCount = count($this->lightStack);
189
        assert($newCount == $stackCount, "Should have $stackCount elements in stack, have $newCount elements");
190
        array_pop($this->lightStack);
191
        return $odata;
192
    }
193
194
    /**
195
     * Write top level feed element.
196
     *
197
     * @param array &$entryObjects Array of entry resources to be written
198
     *
199
     * @return ODataFeed
200
     */
201
    public function writeTopLevelElements(&$entryObjects)
202
    {
203
        assert(is_array($entryObjects), '!is_array($entryObjects)');
204
205
        $this->loadStackIfEmpty();
206
        $setName = $this->getRequest()->getTargetResourceSetWrapper()->getName();
207
208
        $title = $this->getRequest()->getContainerName();
209
        $relativeUri = $this->getRequest()->getIdentifier();
210
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
211
212
        $selfLink = new ODataLink();
213
        $selfLink->name = 'self';
214
        $selfLink->title = $relativeUri;
215
        $selfLink->url = $relativeUri;
216
217
        $odata = new ODataFeed();
218
        $odata->title = $title;
219
        $odata->id = $absoluteUri;
220
        $odata->selfLink = $selfLink;
221
222
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
223
            $odata->rowCount = $this->getRequest()->getCountValue();
224
        }
225
        foreach ($entryObjects as $entry) {
226
            $odata->entries[] = $this->writeTopLevelElement($entry);
227
        }
228
229
230
        if ($this->needNextPageLink(count($entryObjects))) {
231
            $stackSegment = $setName;
232
            $lastObject = end($entryObjects);
233
            $segment = $this->getNextLinkUri($lastObject, $absoluteUri);
234
            $nextLink = new ODataLink();
235
            $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
236
            $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
237
            $odata->nextPageLink = $nextLink;
238
        }
239
240
        return $odata;
241
    }
242
243
    /**
244
     * Write top level url element.
245
     *
246
     * @param mixed $entryObject The entry resource whose url to be written
247
     *
248
     * @return ODataURL
249
     */
250
    public function writeUrlElement($entryObject)
251
    {
252
        $url = new ODataURL();
253
        if (!is_null($entryObject)) {
254
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
255
            $relativeUri = $this->getEntryInstanceKey(
256
                $entryObject,
257
                $currentResourceType,
258
                $this->getCurrentResourceSetWrapper()->getName()
259
            );
260
261
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
262
        }
263
264
        return $url;
265
    }
266
267
    /**
268
     * Write top level url collection.
269
     *
270
     * @param array $entryObjects Array of entry resources
271
     *                            whose url to be written
272
     *
273
     * @return ODataURLCollection
274
     */
275
    public function writeUrlElements($entryObjects)
276
    {
277
        $urls = new ODataURLCollection();
278
        if (!empty($entryObjects)) {
279
            $i = 0;
280
            foreach ($entryObjects as $entryObject) {
281
                $urls->urls[$i] = $this->writeUrlElement($entryObject);
282
                ++$i;
283
            }
284
285
            if ($i > 0 && $this->needNextPageLink(count($entryObjects))) {
286
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
287
                $lastObject = end($entryObjects);
288
                $segment = $this->getNextLinkUri($lastObject, $this->getRequest()->getRequestUrl()->getUrlAsString());
289
                $nextLink = new ODataLink();
290
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
291
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
292
                $urls->nextPageLink = $nextLink;
293
            }
294
        }
295
296
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
297
            $urls->count = $this->getRequest()->getCountValue();
298
        }
299
300
        return $urls;
301
    }
302
303
    /**
304
     * Write top level complex resource.
305
     *
306
     * @param mixed &$complexValue The complex object to be
307
     *                                    written
308
     * @param string $propertyName The name of the
309
     *                                    complex property
310
     * @param ResourceType &$resourceType Describes the type of
311
     *                                    complex object
312
     *
313
     * @return ODataPropertyContent
314
     * @codeCoverageIgnore
315
     */
316
    public function writeTopLevelComplexObject(&$complexValue, $propertyName, ResourceType &$resourceType)
317
    {
318
        // TODO: Figure out if we need to bother implementing this
319
    }
320
321
    /**
322
     * Write top level bag resource.
323
     *
324
     * @param mixed &$BagValue The bag object to be
325
     *                                    written
326
     * @param string $propertyName The name of the
327
     *                                    bag property
328
     * @param ResourceType &$resourceType Describes the type of
329
     *                                    bag object
330
     * @codeCoverageIgnore
331
     * @return ODataPropertyContent
332
     */
333
    public function writeTopLevelBagObject(&$BagValue, $propertyName, ResourceType &$resourceType)
334
    {
335
        // TODO: Figure out if we need to bother implementing this
336
    }
337
338
    /**
339
     * Write top level primitive value.
340
     *
341
     * @param mixed &$primitiveValue The primitve value to be
342
     *                                            written
343
     * @param ResourceProperty &$resourceProperty Resource property
344
     *                                            describing the
345
     *                                            primitive property
346
     *                                            to be written
347
     * @codeCoverageIgnore
348
     * @return ODataPropertyContent
349
     */
350
    public function writeTopLevelPrimitive(&$primitiveValue, ResourceProperty &$resourceProperty = null)
351
    {
352
        // TODO: Figure out if we need to bother implementing this
353
    }
354
355
    /**
356
     * Gets reference to the request submitted by client.
357
     *
358
     * @return RequestDescription
359
     */
360
    public function getRequest()
361
    {
362
        assert(null != $this->request, 'Request not yet set');
363
364
        return $this->request;
365
    }
366
367
    /**
368
     * Sets reference to the request submitted by client.
369
     *
370
     * @param RequestDescription $request
371
     */
372
    public function setRequest(RequestDescription $request)
373
    {
374
        $this->request = $request;
375
        $this->stack->setRequest($request);
376
    }
377
378
    /**
379
     * Gets the data service instance.
380
     *
381
     * @return IService
382
     */
383
    public function getService()
384
    {
385
        return $this->service;
386
    }
387
388
    /**
389
     * Gets the segment stack instance.
390
     *
391
     * @return SegmentStack
392
     */
393
    public function getStack()
394
    {
395
        return $this->stack;
396
    }
397
398
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
399
    {
400
        $typeName = $resourceType->getName();
401
        $keyProperties = $resourceType->getKeyProperties();
402
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
403
        $keyString = $containerName . '(';
404
        $comma = null;
405
        foreach ($keyProperties as $keyName => $resourceProperty) {
406
            $keyType = $resourceProperty->getInstanceType();
407
            assert($keyType instanceof IType, '$keyType not instanceof IType');
408
            $keyName = $resourceProperty->getName();
409
            $keyValue = $entityInstance->$keyName;
410
            if (!isset($keyValue)) {
411
                throw ODataException::createInternalServerError(
412
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
413
                );
414
            }
415
416
            $keyValue = $keyType->convertToOData($keyValue);
417
            $keyString .= $comma . $keyName . '=' . $keyValue;
418
            $comma = ',';
419
        }
420
421
        $keyString .= ')';
422
423
        return $keyString;
424
    }
425
426
    /**
427
     * @param $entryObject
428
     * @param $type
429
     * @param $relativeUri
430
     * @param $resourceType
431
     * @return array
432
     */
433
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
434
    {
435
        $context = $this->getService()->getOperationContext();
436
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
437
        assert(null != $streamProviderWrapper, "Retrieved stream provider must not be null");
438
439
        $mediaLink = null;
440
        if ($resourceType->isMediaLinkEntry()) {
441
            $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...
442
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag);
443
        }
444
        $mediaLinks = [];
445
        if ($resourceType->hasNamedStream()) {
446
            $namedStreams = $resourceType->getAllNamedStreams();
447
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
448
                $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...
449
                    $entryObject,
450
                    $resourceStreamInfo,
451
                    $context,
452
                    $relativeUri
453
                );
454
                $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...
455
                    $entryObject,
456
                    $resourceStreamInfo,
457
                    $context
458
                );
459
                $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...
460
                    $entryObject,
461
                    $resourceStreamInfo,
462
                    $context
463
                );
464
465
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
466
                $mediaLinks[] = $nuLink;
467
            }
468
        }
469
        return [$mediaLink, $mediaLinks];
470
    }
471
472
    /**
473
     * Gets collection of projection nodes under the current node.
474
     *
475
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes
476
     *                                                        describing projections for the current segment, If this method returns
477
     *                                                        null it means no projections are to be applied and the entire resource
478
     *                                                        for the current segment should be serialized, If it returns non-null
479
     *                                                        only the properties described by the returned projection segments should
480
     *                                                        be serialized
481
     */
482
    protected function getProjectionNodes()
483
    {
484
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
485
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
486
            return null;
487
        }
488
489
        return $expandedProjectionNode->getChildNodes();
490
    }
491
492
    /**
493
     * Find a 'ExpandedProjectionNode' instance in the projection tree
494
     * which describes the current segment.
495
     *
496
     * @return ExpandedProjectionNode|null
497
     */
498
    protected function getCurrentExpandedProjectionNode()
499
    {
500
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
501
        if (is_null($expandedProjectionNode)) {
502
            return null;
503
        } else {
504
            $segmentNames = $this->getStack()->getSegmentNames();
505
            $depth = count($segmentNames);
506
            // $depth == 1 means serialization of root entry
507
            //(the resource identified by resource path) is going on,
508
            //so control won't get into the below for loop.
509
            //we will directly return the root node,
510
            //which is 'ExpandedProjectionNode'
511
            // for resource identified by resource path.
512
            if (0 != $depth) {
513
                for ($i = 1; $i < $depth; ++$i) {
514
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]);
515
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
516
                    assert(
517
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
518
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
519
                    );
520
                }
521
            }
522
        }
523
524
        return $expandedProjectionNode;
525
    }
526
527
    /**
528
     * Check whether to expand a navigation property or not.
529
     *
530
     * @param string $navigationPropertyName Name of naviagtion property in question
531
     *
532
     * @return bool True if the given navigation should be
533
     *              explanded otherwise false
534
     */
535
    protected function shouldExpandSegment($navigationPropertyName)
536
    {
537
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
538
        if (is_null($expandedProjectionNode)) {
539
            return false;
540
        }
541
542
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
543
544
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
545
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
546
    }
547
548
    /**
549
     * Wheter next link is needed for the current resource set (feed)
550
     * being serialized.
551
     *
552
     * @param int $resultSetCount Number of entries in the current
553
     *                            resource set
554
     *
555
     * @return bool true if the feed must have a next page link
556
     */
557
    protected function needNextPageLink($resultSetCount)
558
    {
559
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
560
        $recursionLevel = count($this->getStack()->getSegmentNames());
561
        $pageSize = $currentResourceSet->getResourceSetPageSize();
562
563
        if (1 == $recursionLevel) {
564
            //presence of $top option affect next link for root container
565
            $topValueCount = $this->getRequest()->getTopOptionCount();
566
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
567
                return false;
568
            }
569
        }
570
        return $resultSetCount == $pageSize;
571
    }
572
573
    /**
574
     * Resource set wrapper for the resource being serialized.
575
     *
576
     * @return ResourceSetWrapper
577
     */
578
    protected function getCurrentResourceSetWrapper()
579
    {
580
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
581
        $count = count($segmentWrappers);
582
583
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
584
    }
585
586
    /**
587
     * Get next page link from the given entity instance.
588
     *
589
     * @param mixed  &$lastObject Last object serialized to be
590
     *                            used for generating $skiptoken
591
     * @param string $absoluteUri Absolute response URI
592
     *
593
     * @return string for the link for next page
594
     */
595
    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...
596
    {
597
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
598
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
599
        assert(null != $internalOrderByInfo);
600
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
601
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
602
603
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
604
        assert(!is_null($skipToken), '!is_null($skipToken)');
605
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
606
        $skipToken = '?'.$queryParameterString.$token.$skipToken;
607
608
        return $skipToken;
609
    }
610
611
    /**
612
     * Builds the string corresponding to query parameters for top level results
613
     * (result set identified by the resource path) to be put in next page link.
614
     *
615
     * @return string|null string representing the query parameters in the URI
616
     *                     query parameter format, NULL if there
617
     *                     is no query parameters
618
     *                     required for the next link of top level result set
619
     */
620
    protected function getNextPageLinkQueryParametersForRootResourceSet()
621
    {
622
        $queryParameterString = null;
623
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
624
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
625
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
626
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
627
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
628
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
629
            if (!is_null($value)) {
630
                if (!is_null($queryParameterString)) {
631
                    $queryParameterString = $queryParameterString . '&';
632
                }
633
634
                $queryParameterString .= $queryOption . '=' . $value;
635
            }
636
        }
637
638
        $topCountValue = $this->getRequest()->getTopOptionCount();
639
        if (!is_null($topCountValue)) {
640
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
641
            if (!is_null($queryParameterString)) {
642
                $queryParameterString .= '&';
643
            }
644
645
            $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
646
        }
647
648
        if (!is_null($queryParameterString)) {
649
            $queryParameterString .= '&';
650
        }
651
652
        return $queryParameterString;
653
    }
654
655
    private function loadStackIfEmpty()
656
    {
657
        if (0 == count($this->lightStack)) {
658
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
659
            array_push($this->lightStack, [$typeName, $typeName]);
660
        }
661
    }
662
663
    /**
664
     * Convert the given primitive value to string.
665
     * Note: This method will not handle null primitive value.
666
     *
667
     * @param IType &$primitiveResourceType        Type of the primitive property
668
     *                                             whose value need to be converted
669
     * @param mixed        $primitiveValue         Primitive value to convert
670
     *
671
     * @return string
672
     */
673
    private function primitiveToString(IType &$type, $primitiveValue)
674
    {
675
        if ($type instanceof Boolean) {
676
            $stringValue = (true === $primitiveValue) ? 'true' : 'false';
677
        } elseif ($type instanceof Binary) {
678
            $stringValue = base64_encode($primitiveValue);
679
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
680
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
681
        } elseif ($type instanceof StringType) {
682
            $stringValue = utf8_encode($primitiveValue);
683
        } else {
684
            $stringValue = strval($primitiveValue);
685
        }
686
687
        return $stringValue;
688
    }
689
690
    /**
691
     * @param $entryObject
692
     * @param $nonRelProp
693
     * @return ODataPropertyContent
694
     */
695
    private function writePrimitiveProperties($entryObject, $nonRelProp)
696
    {
697
        $propertyContent = new ODataPropertyContent();
698
        $cereal = $this->modelSerialiser->bulkSerialise($entryObject);
699
        foreach ($cereal as $corn => $flake) {
700
            if (!array_key_exists($corn, $nonRelProp)) {
701
                continue;
702
            }
703
            $corn = strval($corn);
704
            $rType = $nonRelProp[$corn]->getResourceType()->getInstanceType();
705
            $subProp = new ODataProperty();
706
            $subProp->name = $corn;
707
            $subProp->value = isset($flake) ? $this->primitiveToString($rType, $flake) : null;
708
            $subProp->typeName = $nonRelProp[$corn]->getResourceType()->getFullName();
709
            $propertyContent->properties[] = $subProp;
710
        }
711
        return $propertyContent;
712
    }
713
714
    /**
715
     * @param $entryObject
716
     * @param $prop
717
     * @param $nuLink
718
     * @param $propKind
719
     * @param $propName
720
     */
721
    private function expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName)
722
    {
723
        $nextName = $prop->getResourceType()->getName();
724
        $nuLink->isExpanded = true;
725
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
726
        $nuLink->isCollection = $isCollection;
727
        $value = $entryObject->$propName;
728
        array_push($this->lightStack, [$nextName, $propName]);
729
        if (!$isCollection) {
730
            $expandedResult = $this->writeTopLevelElement($value);
731
        } else {
732
            $expandedResult = $this->writeTopLevelElements($value);
733
        }
734
        $nuLink->expandedResult = $expandedResult;
735
        if (!isset($nuLink->expandedResult)) {
736
            $nuLink->isCollection = null;
737
            $nuLink->isExpanded = null;
738
        } else {
739
            if (isset($nuLink->expandedResult->selfLink)) {
740
                $nuLink->expandedResult->selfLink->title = $propName;
741
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
742
                $nuLink->expandedResult->title = $propName;
743
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
744
            }
745
        }
746
    }
747
748
    /**
749
     * Gets the data service instance.
750
     *
751
     * @return IService
752
     */
753
    public function setService(IService $service)
754
    {
755
        $this->service = $service;
756
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
757
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
758
    }
759
}
760