Test Setup Failed
Pull Request — master (#147)
by Alex
03:48
created

CynicSerialiser::getUpdated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace POData\ObjectModel;
4
5
use Carbon\Carbon;
6
use POData\Common\InvalidOperationException;
7
use POData\Common\Messages;
8
use POData\Common\ODataConstants;
9
use POData\Common\ODataException;
10
use POData\IService;
11
use POData\Providers\Metadata\ResourceComplexType;
12
use POData\Providers\Metadata\ResourceEntityType;
13
use POData\Providers\Metadata\ResourcePrimitiveType;
14
use POData\Providers\Metadata\ResourceProperty;
15
use POData\Providers\Metadata\ResourcePropertyKind;
16
use POData\Providers\Metadata\ResourceSet;
17
use POData\Providers\Metadata\ResourceSetWrapper;
18
use POData\Providers\Metadata\ResourceType;
19
use POData\Providers\Metadata\ResourceTypeKind;
20
use POData\Providers\Metadata\Type\Binary;
21
use POData\Providers\Metadata\Type\Boolean;
22
use POData\Providers\Metadata\Type\DateTime;
23
use POData\Providers\Metadata\Type\IType;
24
use POData\Providers\Metadata\Type\StringType;
25
use POData\Providers\Query\QueryResult;
26
use POData\Providers\Query\QueryType;
27
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
28
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode;
29
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\RootProjectionNode;
30
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
31
use POData\UriProcessor\RequestDescription;
32
use POData\UriProcessor\SegmentStack;
33
34
class CynicSerialiser implements IObjectSerialiser
35
{
36
    /**
37
     * The service implementation.
38
     *
39
     * @var IService
40
     */
41
    protected $service;
42
43
    /**
44
     * Request description instance describes OData request the
45
     * the client has submitted and result of the request.
46
     *
47
     * @var RequestDescription
48
     */
49
    protected $request;
50
51
    /**
52
     * Collection of complex type instances used for cycle detection.
53
     *
54
     * @var array
55
     */
56
    protected $complexTypeInstanceCollection;
57
58
    /**
59
     * Absolute service Uri.
60
     *
61
     * @var string
62
     */
63
    protected $absoluteServiceUri;
64
65
    /**
66
     * Absolute service Uri with slash.
67
     *
68
     * @var string
69
     */
70
    protected $absoluteServiceUriWithSlash;
71
72
    /**
73
     * Holds reference to segment stack being processed.
74
     *
75
     * @var SegmentStack
76
     */
77
    protected $stack;
78
79
    /**
80
     * Lightweight stack tracking for recursive descent fill.
81
     */
82
    private $lightStack = [];
83
84
    /*
85
     * Update time to insert into ODataEntry/ODataFeed fields
86
     * @var \DateTime;
87
     */
88
    private $updated;
89
90
    /**
91
     * @param IService                $service Reference to the data service instance
92
     * @param RequestDescription|null $request Type instance describing the client submitted request
93
     */
94
    public function __construct(IService $service, RequestDescription $request = null)
95
    {
96
        $this->service = $service;
97
        $this->request = $request;
98
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
99
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
100
        $this->stack = new SegmentStack($request);
101
        $this->complexTypeInstanceCollection = [];
102
        $this->updated = Carbon::now();
103
    }
104
105
    /**
106
     * Write a top level entry resource.
107
     *
108
     * @param QueryResult $entryObject Results property contains reference to the entry object to be written
109
     *
110
     * @return ODataEntry|null
111
     */
112
    public function writeTopLevelElement(QueryResult $entryObject)
113
    {
114
        if (!isset($entryObject->results)) {
115
            array_pop($this->lightStack);
116
            return null;
117
        }
118
119
        $this->loadStackIfEmpty();
120
121
        $stackCount = count($this->lightStack);
122
        $topOfStack = $this->lightStack[$stackCount-1];
123
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack[0]);
124
        assert($resourceType instanceof ResourceType, get_class($resourceType));
125
        $rawProp = $resourceType->getAllProperties();
126
        $relProp = [];
127
        $nonRelProp = [];
128
        $last = end($this->lightStack);
129
        $projNodes = ($last[0] == $last[1]) ? $this->getProjectionNodes() : null;
130
131
        foreach ($rawProp as $prop) {
132
            $propName = $prop->getName();
133
            if ($prop->getResourceType() instanceof ResourceEntityType) {
134
                $relProp[$propName] = $prop;
135
            } else {
136
                $nonRelProp[$propName] = $prop;
137
            }
138
        }
139
        $rawCount = count($rawProp);
140
        $relCount = count($relProp);
141
        $nonRelCount = count($nonRelProp);
142
        assert(
143
            $rawCount == $relCount + $nonRelCount,
144
            'Raw property count '.$rawCount.', does not equal sum of relProp count, '.$relCount
145
            .', and nonRelPropCount,'.$nonRelCount
146
        );
147
148
        // now mask off against projNodes
149
        if (null !== $projNodes) {
150
            $keys = [];
151
            foreach ($projNodes as $node) {
152
                $keys[$node->getPropertyName()] = '';
153
            }
154
155
            $relProp = array_intersect_key($relProp, $keys);
156
            $nonRelProp = array_intersect_key($nonRelProp, $keys);
157
        }
158
159
        $resourceSet = $resourceType->getCustomState();
160
        assert($resourceSet instanceof ResourceSet);
161
        $title = $resourceType->getName();
162
        $type = $resourceType->getFullName();
163
164
        $relativeUri = $this->getEntryInstanceKey(
165
            $entryObject->results,
166
            $resourceType,
167
            $resourceSet->getName()
168
        );
169
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
170
171
        list($mediaLink, $mediaLinks) = $this->writeMediaData(
172
            $entryObject->results,
173
            $type,
174
            $relativeUri,
175
            $resourceType
176
        );
177
178
        $propertyContent = $this->writeProperties($entryObject->results, $nonRelProp);
179
180
        $links = [];
181
        foreach ($relProp as $prop) {
182
            $nuLink = new ODataLink();
183
            $propKind = $prop->getKind();
184
185
            assert(
186
                ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
187
                || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind,
188
                '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
189
                .' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE'
190
            );
191
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
192
            $propType = 'application/atom+xml;type='.$propTail;
193
            $propName = $prop->getName();
194
            $nuLink->title = $propName;
195
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
196
            $nuLink->url = $relativeUri . '/' . $propName;
197
            $nuLink->type = $propType;
198
199
            $navProp = new ODataNavigationPropertyInfo($prop, $this->shouldExpandSegment($propName));
200
            if ($navProp->expanded) {
201
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
202
            }
203
204
            $links[] = $nuLink;
205
        }
206
207
        $odata = new ODataEntry();
208
        $odata->resourceSetName = $resourceSet->getName();
209
        $odata->id = $absoluteUri;
210
        $odata->title = new ODataTitle($title);
211
        $odata->type = $type;
212
        $odata->propertyContent = $propertyContent;
213
        $odata->isMediaLinkEntry = true === $resourceType->isMediaLinkEntry() ? true : null;
214
        $odata->editLink = $relativeUri;
215
        $odata->mediaLink = $mediaLink;
216
        $odata->mediaLinks = $mediaLinks;
217
        $odata->links = $links;
218
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
219
220
        $newCount = count($this->lightStack);
221
        assert(
222
            $newCount == $stackCount,
223
            'Should have ' . $stackCount . 'elements in stack, have ' . $newCount . 'elements'
224
        );
225
        array_pop($this->lightStack);
226
        return $odata;
227
    }
228
229
    /**
230
     * Write top level feed element.
231
     *
232
     * @param QueryResult &$entryObjects Results property contains array of entry resources to be written
233
     *
234
     * @return ODataFeed
235
     */
236
    public function writeTopLevelElements(QueryResult &$entryObjects)
237
    {
238
        assert(is_array($entryObjects->results), '!is_array($entryObjects->results)');
239
240
        $this->loadStackIfEmpty();
241
        $setName = $this->getRequest()->getTargetResourceSetWrapper()->getName();
242
243
        $title = $this->getRequest()->getContainerName();
244
        $relativeUri = $this->getRequest()->getIdentifier();
245
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
246
247
        $selfLink = new ODataLink();
248
        $selfLink->name = 'self';
249
        $selfLink->title = $title;
250
        $selfLink->url = $relativeUri;
251
252
        $odata = new ODataFeed();
253
        $odata->title = new ODataTitle($title);
254
        $odata->id = $absoluteUri;
255
        $odata->selfLink = $selfLink;
256
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
257
258
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
259
            $odata->rowCount = $this->getRequest()->getCountValue();
260
        }
261
        foreach ($entryObjects->results as $entry) {
262
            if (!$entry instanceof QueryResult) {
263
                $query = new QueryResult();
264
                $query->results = $entry;
265
            } else {
266
                $query = $entry;
267
            }
268
            $odata->entries[] = $this->writeTopLevelElement($query);
269
        }
270
271
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
272
        $requestTop = $this->getRequest()->getTopOptionCount();
273
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
274
        $requestTop = (null === $requestTop) ? $pageSize + 1 : $requestTop;
275
276
        if (true === $entryObjects->hasMore && $requestTop > $pageSize) {
277
            $stackSegment = $setName;
278
            $lastObject = end($entryObjects->results);
279
            $segment = $this->getNextLinkUri($lastObject);
280
            $nextLink = new ODataLink();
281
            $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
282
            $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
283
            $odata->nextPageLink = $nextLink;
284
        }
285
286
        return $odata;
287
    }
288
289
    /**
290
     * Write top level url element.
291
     *
292
     * @param QueryResult $entryObject Results property contains the entry resource whose url to be written
293
     *
294
     * @return ODataURL
295
     */
296
    public function writeUrlElement(QueryResult $entryObject)
297
    {
298
        $url = new ODataURL();
299
        if (null !== $entryObject->results) {
300
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
301
            $relativeUri = $this->getEntryInstanceKey(
302
                $entryObject->results,
303
                $currentResourceType,
304
                $this->getCurrentResourceSetWrapper()->getName()
305
            );
306
307
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
308
        }
309
310
        return $url;
311
    }
312
313
    /**
314
     * Write top level url collection.
315
     *
316
     * @param QueryResult $entryObjects Results property contains the array of entry resources whose urls are
317
     *                                  to be written
318
     *
319
     * @return ODataURLCollection
320
     */
321
    public function writeUrlElements(QueryResult $entryObjects)
322
    {
323
        $urls = new ODataURLCollection();
324
        if (!empty($entryObjects->results)) {
325
            $i = 0;
326
            foreach ($entryObjects->results as $entryObject) {
327
                $urls->urls[$i] = $this->writeUrlElement($entryObject);
328
                ++$i;
329
            }
330
331
            if ($i > 0 && true === $entryObjects->hasMore) {
332
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
333
                $lastObject = end($entryObjects->results);
334
                $segment = $this->getNextLinkUri($lastObject);
335
                $nextLink = new ODataLink();
336
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
337
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
338
                $nextLink->url = ltrim($nextLink->url, '/');
339
                $urls->nextPageLink = $nextLink;
340
            }
341
        }
342
343
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
344
            $urls->count = $this->getRequest()->getCountValue();
345
        }
346
347
        return $urls;
348
    }
349
350
    /**
351
     * Write top level complex resource.
352
     *
353
     * @param QueryResult  &$complexValue Results property contains the complex object to be written
354
     * @param string       $propertyName  The name of the complex property
355
     * @param ResourceType &$resourceType Describes the type of complex object
356
     *
357
     * @return ODataPropertyContent
358
     */
359
    public function writeTopLevelComplexObject(QueryResult &$complexValue, $propertyName, ResourceType &$resourceType)
360
    {
361
        $result = $complexValue->results;
362
363
        $propertyContent = new ODataPropertyContent();
364
        $odataProperty = new ODataProperty();
365
        $odataProperty->name = $propertyName;
366
        $odataProperty->typeName = $resourceType->getFullName();
367
        if (null !== $result) {
368
            assert(is_object($result), 'Supplied $customObject must be an object');
369
            $internalContent = $this->writeComplexValue($resourceType, $result);
370
            $odataProperty->value = $internalContent;
371
        }
372
373
        $propertyContent->properties[] = $odataProperty;
374
375
        return $propertyContent;
376
    }
377
378
    /**
379
     * Write top level bag resource.
380
     *
381
     * @param QueryResult  $bagValue
382
     * @param string       $propertyName  The name of the bag property
383
     * @param ResourceType &$resourceType Describes the type of bag object
384
     *
385
     * @return ODataPropertyContent
386
     */
387
    public function writeTopLevelBagObject(QueryResult &$bagValue, $propertyName, ResourceType &$resourceType)
388
    {
389
        $result = $bagValue->results;
390
391
        $propertyContent = new ODataPropertyContent();
392
        $odataProperty = new ODataProperty();
393
        $odataProperty->name = $propertyName;
394
        $odataProperty->typeName = 'Collection('.$resourceType->getFullName().')';
395
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
396
397
        $propertyContent->properties[] = $odataProperty;
398
        return $propertyContent;
399
    }
400
401
    /**
402
     * Write top level primitive value.
403
     *
404
     * @param QueryResult      &$primitiveValue   Results property contains the primitive value to be written
405
     * @param ResourceProperty &$resourceProperty Resource property describing the primitive property to be written
406
     *
407
     * @return ODataPropertyContent
408
     */
409
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
410
    {
411
        assert(null !== $resourceProperty, 'Resource property must not be null');
412
        $result = new ODataPropertyContent();
413
        $property = new ODataProperty();
414
        $property->name = $resourceProperty->getName();
415
416
        $iType = $resourceProperty->getInstanceType();
417
        assert($iType instanceof IType, get_class($iType));
418
        $property->typeName = $iType->getFullTypeName();
419
        if (null !== $primitiveValue->results) {
420
            $rType = $resourceProperty->getResourceType()->getInstanceType();
421
            assert($rType instanceof IType, get_class($rType));
422
            $property->value = $this->primitiveToString($rType, $primitiveValue->results);
423
        }
424
425
        $result->properties[] = $property;
426
        return $result;
427
    }
428
429
    /**
430
     * Gets reference to the request submitted by client.
431
     *
432
     * @return RequestDescription
433
     */
434
    public function getRequest()
435
    {
436
        assert(null !== $this->request, 'Request not yet set');
437
438
        return $this->request;
439
    }
440
441
    /**
442
     * Sets reference to the request submitted by client.
443
     *
444
     * @param  RequestDescription $request
445
     * @return void
446
     */
447
    public function setRequest(RequestDescription $request)
448
    {
449
        $this->request = $request;
450
        $this->stack->setRequest($request);
451
    }
452
453
    /**
454
     * Gets the data service instance.
455
     *
456
     * @return IService
457
     */
458
    public function getService()
459
    {
460
        return $this->service;
461
    }
462
463
    /**
464
     * Sets the data service instance.
465
     *
466
     * @param  IService $service
467
     * @return void
468
     */
469
    public function setService(IService $service)
470
    {
471
        $this->service = $service;
472
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
473
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
474
    }
475
476
    /**
477
     * Gets the segment stack instance.
478
     *
479
     * @return SegmentStack
480
     */
481
    public function getStack()
482
    {
483
        return $this->stack;
484
    }
485
486
    /**
487
     * Get update timestamp
488
     *
489
     * @return Carbon
490
     */
491
    public function getUpdated()
492
    {
493
        return $this->updated;
494
    }
495
496
    /**
497
     * @param ResourceType $resourceType
498
     * @param $result
499
     * @return ODataBagContent|null
500
     */
501
    protected function writeBagValue(ResourceType &$resourceType, $result)
502
    {
503
        assert(null === $result || is_array($result), 'Bag parameter must be null or array');
504
        $typeKind = $resourceType->getResourceTypeKind();
505
        assert(
506
            ResourceTypeKind::PRIMITIVE() == $typeKind || ResourceTypeKind::COMPLEX() == $typeKind,
507
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
508
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
509
        );
510
        if (null == $result) {
511
            return null;
512
        }
513
        $bag = new ODataBagContent();
514
        foreach ($result as $value) {
515
            if (isset($value)) {
516
                if (ResourceTypeKind::PRIMITIVE() == $typeKind) {
517
                    $instance = $resourceType->getInstanceType();
518
                    assert($instance instanceof IType, get_class($instance));
519
                    $bag->propertyContents[] = $this->primitiveToString($instance, $value);
520
                } elseif (ResourceTypeKind::COMPLEX() == $typeKind) {
521
                    $bag->propertyContents[] = $this->writeComplexValue($resourceType, $value);
522
                }
523
            }
524
        }
525
        return $bag;
526
    }
527
528
    /**
529
     * @param  ResourceType         $resourceType
530
     * @param  object               $result
531
     * @param  string|null          $propertyName
532
     * @return ODataPropertyContent
533
     */
534
    protected function writeComplexValue(ResourceType &$resourceType, &$result, $propertyName = null)
535
    {
536
        assert(is_object($result), 'Supplied $customObject must be an object');
537
538
        $count = count($this->complexTypeInstanceCollection);
539
        for ($i = 0; $i < $count; ++$i) {
540
            if ($this->complexTypeInstanceCollection[$i] === $result) {
541
                throw new InvalidOperationException(
542
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
543
                );
544
            }
545
        }
546
547
        $this->complexTypeInstanceCollection[$count] = &$result;
548
549
        $internalContent = new ODataPropertyContent();
550
        $resourceProperties = $resourceType->getAllProperties();
551
        // first up, handle primitive properties
552
        foreach ($resourceProperties as $prop) {
553
            $resourceKind = $prop->getKind();
554
            $propName = $prop->getName();
555
            $internalProperty = new ODataProperty();
556
            $internalProperty->name = $propName;
557
            $raw = $result->$propName;
558
            if (static::isMatchPrimitive($resourceKind)) {
559
                $iType = $prop->getInstanceType();
560
                assert($iType instanceof IType, get_class($iType));
561
                $internalProperty->typeName = $iType->getFullTypeName();
562
563
                $rType = $prop->getResourceType()->getInstanceType();
564
                assert($rType instanceof IType, get_class($rType));
565
                if (null !== $raw) {
566
                    $internalProperty->value = $this->primitiveToString($rType, $raw);
567
                }
568
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
569
                $rType = $prop->getResourceType();
570
                $internalProperty->typeName = $rType->getFullName();
571
                if (null !== $raw) {
572
                    $internalProperty->value = $this->writeComplexValue($rType, $raw, $propName);
573
                }
574
            }
575
            $internalContent->properties[] = $internalProperty;
576
        }
577
578
        unset($this->complexTypeInstanceCollection[$count]);
579
        return $internalContent;
580
    }
581
582
    /**
583
     * Check whether to expand a navigation property or not.
584
     *
585
     * @param string $navigationPropertyName Name of naviagtion property in question
586
     *
587
     * @return bool True if the given navigation should be expanded, otherwise false
588
     */
589
    protected function shouldExpandSegment($navigationPropertyName)
590
    {
591
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
592
        if (null === $expandedProjectionNode) {
593
            return false;
594
        }
595
596
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
597
598
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
599
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
600
    }
601
602
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
603
    {
604
        $typeName = $resourceType->getName();
605
        $keyProperties = $resourceType->getKeyProperties();
606
        assert(0 != count($keyProperties), 'count($keyProperties) == 0');
607
        $keyString = $containerName . '(';
608
        $comma = null;
609
        foreach ($keyProperties as $keyName => $resourceProperty) {
610
            $keyType = $resourceProperty->getInstanceType();
611
            assert($keyType instanceof IType, '$keyType not instanceof IType');
612
            $keyName = $resourceProperty->getName();
613
            $keyValue = $entityInstance->$keyName;
614
            if (!isset($keyValue)) {
615
                $msg = Messages::badQueryNullKeysAreNotSupported($typeName, $keyName);
616
                throw ODataException::createInternalServerError($msg);
617
            }
618
619
            $keyValue = $keyType->convertToOData($keyValue);
620
            $keyString .= $comma . $keyName . '=' . $keyValue;
621
            $comma = ',';
622
        }
623
624
        $keyString .= ')';
625
626
        return $keyString;
627
    }
628
629
    /**
630
     * Resource set wrapper for the resource being serialized.
631
     *
632
     * @return ResourceSetWrapper
633
     */
634 View Code Duplication
    protected function getCurrentResourceSetWrapper()
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...
635
    {
636
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
637
        $count = count($segmentWrappers);
638
639
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
640
    }
641
642
    /**
643
     * Wheter next link is needed for the current resource set (feed)
644
     * being serialized.
645
     *
646
     * @param int $resultSetCount Number of entries in the current
647
     *                            resource set
648
     *
649
     * @return bool true if the feed must have a next page link
650
     */
651
    protected function needNextPageLink($resultSetCount)
0 ignored issues
show
Coding Style introduced by
function needNextPageLink() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
652
    {
653
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
654
        $recursionLevel = count($this->getStack()->getSegmentNames());
655
        $pageSize = $currentResourceSet->getResourceSetPageSize();
656
657
        if (1 == $recursionLevel) {
658
            //presence of $top option affect next link for root container
659
            $topValueCount = $this->getRequest()->getTopOptionCount();
660
            if (null !== $topValueCount && ($topValueCount <= $pageSize)) {
661
                return false;
662
            }
663
        }
664
        return $resultSetCount == $pageSize;
665
    }
666
667
    /**
668
     * Get next page link from the given entity instance.
669
     *
670
     * @param mixed &$lastObject Last object serialized to be
671
     *                           used for generating $skiptoken
672
     *
673
     * @return string for the link for next page
674
     */
675
    protected function getNextLinkUri(&$lastObject)
676
    {
677
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
678
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
679
        assert(null !== $internalOrderByInfo);
680
        assert(is_object($internalOrderByInfo));
681
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
682
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
683
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
684
685
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
686
        assert(null !== $skipToken, '!is_null($skipToken)');
687
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
688
        $skipToken = '?'.$queryParameterString.$token.$skipToken;
689
690
        return $skipToken;
691
    }
692
693
    /**
694
     * @param $entryObject
695
     * @param $type
696
     * @param $relativeUri
697
     * @param $resourceType
698
     * @return array<ODataMediaLink|null|array>
699
     */
700
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
701
    {
702
        $context = $this->getService()->getOperationContext();
703
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
704
        assert(null != $streamProviderWrapper, 'Retrieved stream provider must not be null');
705
706
        $mediaLink = null;
707
        if ($resourceType->isMediaLinkEntry()) {
708
            $eTag = $streamProviderWrapper->getStreamETag2($entryObject, null, $context);
709
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag);
710
        }
711
        $mediaLinks = [];
712
        if ($resourceType->hasNamedStream()) {
713
            $namedStreams = $resourceType->getAllNamedStreams();
714
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
715
                $readUri = $streamProviderWrapper->getReadStreamUri2(
716
                    $entryObject,
717
                    $resourceStreamInfo,
718
                    $context,
719
                    $relativeUri
720
                );
721
                $mediaContentType = $streamProviderWrapper->getStreamContentType2(
722
                    $entryObject,
723
                    $resourceStreamInfo,
724
                    $context
725
                );
726
                $eTag = $streamProviderWrapper->getStreamETag2(
727
                    $entryObject,
728
                    $resourceStreamInfo,
729
                    $context
730
                );
731
732
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
733
                $mediaLinks[] = $nuLink;
734
            }
735
        }
736
        return [$mediaLink, $mediaLinks];
737
    }
738
739
    /**
740
     * @param $entryObject
741
     * @param $prop
742
     * @param $nuLink
743
     * @param $propKind
744
     * @param $propName
745
     */
746
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
747
    {
748
        $nextName = $prop->getResourceType()->getName();
749
        $nuLink->isExpanded = true;
750
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
751
        $nuLink->isCollection = $isCollection;
752
        $value = $entryObject->results->$propName;
753
        $nullResult = null === $value;
754
        $result = new QueryResult();
755
        $result->results = $value;
756
        if (!$nullResult) {
757
            array_push($this->lightStack, [$nextName, $propName]);
758
            if (isset($value)) {
759
                if (!$isCollection) {
760
                    $expandedResult = $this->writeTopLevelElement($result);
761
                } else {
762
                    $expandedResult = $this->writeTopLevelElements($result);
763
                }
764
                $nuLink->expandedResult = $expandedResult;
765
            }
766
        }
767
        if (!isset($nuLink->expandedResult)) {
768
            $nuLink->isCollection = null;
769
            $nuLink->isExpanded = null;
770
        } else {
771
            if (isset($nuLink->expandedResult->selfLink)) {
772
                $nuLink->expandedResult->selfLink->title = $propName;
773
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
774
                $nuLink->expandedResult->title = new ODataTitle($propName);
775
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
776
            }
777
        }
778
    }
779
780
    /**
781
     * Gets collection of projection nodes under the current node.
782
     *
783
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes describing projections for the current
784
     *                                                        segment, If this method returns null it means no
785
     *                                                        projections are to be applied and the entire resource for
786
     *                                                        the current segment should be serialized, If it returns
787
     *                                                        non-null only the properties described by the returned
788
     *                                                        projection segments should be serialized
789
     */
790
    protected function getProjectionNodes()
791
    {
792
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
793
        if (null === $expandedProjectionNode || $expandedProjectionNode->canSelectAllProperties()) {
794
            return null;
795
        }
796
797
        return $expandedProjectionNode->getChildNodes();
798
    }
799
800
    /**
801
     * Find a 'ExpandedProjectionNode' instance in the projection tree
802
     * which describes the current segment.
803
     *
804
     * @return null|RootProjectionNode|ExpandedProjectionNode
805
     */
806 View Code Duplication
    protected function getCurrentExpandedProjectionNode()
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...
807
    {
808
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
809
        if (null === $expandedProjectionNode) {
810
            return null;
811
        } else {
812
            $segmentNames = $this->getStack()->getSegmentNames();
813
            $depth = count($segmentNames);
814
            // $depth == 1 means serialization of root entry
815
            //(the resource identified by resource path) is going on,
816
            //so control won't get into the below for loop.
817
            //we will directly return the root node,
818
            //which is 'ExpandedProjectionNode'
819
            // for resource identified by resource path.
820
            if (0 != $depth) {
821
                for ($i = 1; $i < $depth; ++$i) {
822
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]);
823
                    assert(null !== $expandedProjectionNode, 'is_null($expandedProjectionNode)');
824
                    assert(
825
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
826
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
827
                    );
828
                }
829
            }
830
        }
831
832
        return $expandedProjectionNode;
833
    }
834
835
    /**
836
     * Builds the string corresponding to query parameters for top level results
837
     * (result set identified by the resource path) to be put in next page link.
838
     *
839
     * @return string|null string representing the query parameters in the URI
840
     *                     query parameter format, NULL if there
841
     *                     is no query parameters
842
     *                     required for the next link of top level result set
843
     */
844
    protected function getNextPageLinkQueryParametersForRootResourceSet()
845
    {
846
        $queryParameterString = null;
847
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
848
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
849
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
850
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
851
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
852
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
853
            if (null !== $value) {
854
                if (null !== $queryParameterString) {
855
                    $queryParameterString = $queryParameterString . '&';
856
                }
857
858
                $queryParameterString .= $queryOption . '=' . $value;
859
            }
860
        }
861
862
        $topCountValue = $this->getRequest()->getTopOptionCount();
863
        if (null !== $topCountValue) {
864
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
865
            if (0 < $remainingCount) {
866
                if (null !== $queryParameterString) {
867
                    $queryParameterString .= '&';
868
                }
869
870
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
871
            }
872
        }
873
874
        if (null !== $queryParameterString) {
875
            $queryParameterString .= '&';
876
        }
877
878
        return $queryParameterString;
879
    }
880
881
    /**
882
     * @param $entryObject
883
     * @param $nonRelProp
884
     * @return ODataPropertyContent
885
     */
886
    private function writeProperties($entryObject, $nonRelProp)
887
    {
888
        $propertyContent = new ODataPropertyContent();
889
        foreach ($nonRelProp as $corn => $flake) {
890
            $resource = $nonRelProp[$corn]->getResourceType();
891
            if ($resource instanceof ResourceEntityType) {
892
                continue;
893
            }
894
            $result = $entryObject->$corn;
895
            $isBag = $flake->isKindOf(ResourcePropertyKind::BAG);
896
            $typePrepend = $isBag ? 'Collection(' : '';
897
            $typeAppend = $isBag ? ')' : '';
898
            $nonNull = null !== $result;
899
            $subProp = new ODataProperty();
900
            $subProp->name = strval($corn);
901
            $subProp->typeName = $typePrepend . $resource->getFullName() . $typeAppend;
902
903
            if ($nonNull && is_array($result)) {
904
                $subProp->value = $this->writeBagValue($resource, $result);
905 View Code Duplication
            } elseif ($resource instanceof ResourcePrimitiveType && $nonNull) {
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...
906
                $rType = $resource->getInstanceType();
907
                assert($rType instanceof IType, get_class($rType));
908
                $subProp->value = $this->primitiveToString($rType, $result);
909
            } elseif ($resource instanceof ResourceComplexType && $nonNull) {
910
                $subProp->value = $this->writeComplexValue($resource, $result, $flake->getName());
911
            }
912
            $propertyContent->properties[] = $subProp;
913
        }
914
        return $propertyContent;
915
    }
916
917
    /**
918
     * @return void
919
     */
920
    private function loadStackIfEmpty()
921
    {
922
        if (0 == count($this->lightStack)) {
923
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
924
            array_push($this->lightStack, [$typeName, $typeName]);
925
        }
926
    }
927
928
    /**
929
     * Convert the given primitive value to string.
930
     * Note: This method will not handle null primitive value.
931
     *
932
     * @param IType &$primitiveResourceType Type of the primitive property
933
     *                                      whose value need to be converted
934
     * @param mixed $primitiveValue         Primitive value to convert
935
     *
936
     * @return string
937
     */
938
    private function primitiveToString(IType &$type, $primitiveValue)
939
    {
940
        if ($type instanceof Boolean) {
941
            $stringValue = (true === $primitiveValue) ? 'true' : 'false';
942
        } elseif ($type instanceof Binary) {
943
            $stringValue = base64_encode($primitiveValue);
944
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
945
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
946
        } elseif ($type instanceof StringType) {
947
            $stringValue = utf8_encode($primitiveValue);
948
        } else {
949
            $stringValue = strval($primitiveValue);
950
        }
951
952
        return $stringValue;
953
    }
954
955
    public static function isMatchPrimitive($resourceKind)
956
    {
957
        if (16 > $resourceKind) {
958
            return false;
959
        }
960
        if (28 < $resourceKind) {
961
            return false;
962
        }
963
        return 0 == ($resourceKind % 4);
964
    }
965
}
966