Completed
Push — master ( a28bca...9fed44 )
by Christopher
02:53
created

CynicSerialiser::writeTopLevelPrimitive()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 19
rs 9.4285
cc 2
eloc 14
nc 2
nop 2
1
<?php
2
3
namespace POData\ObjectModel;
4
5
use Carbon\Carbon;
6
use Illuminate\Support\Collection;
7
use POData\Common\InvalidOperationException;
8
use POData\Common\Messages;
9
use POData\Common\ODataConstants;
10
use POData\Common\ODataException;
11
use POData\IService;
12
use POData\Providers\Metadata\ResourceComplexType;
13
use POData\Providers\Metadata\ResourceEntityType;
14
use POData\Providers\Metadata\ResourcePrimitiveType;
15
use POData\Providers\Metadata\ResourceProperty;
16
use POData\Providers\Metadata\ResourcePropertyKind;
17
use POData\Providers\Metadata\ResourceSet;
18
use POData\Providers\Metadata\ResourceSetWrapper;
19
use POData\Providers\Metadata\ResourceType;
20
use POData\Providers\Metadata\ResourceTypeKind;
21
use POData\Providers\Metadata\Type\Binary;
22
use POData\Providers\Metadata\Type\Boolean;
23
use POData\Providers\Metadata\Type\DateTime;
24
use POData\Providers\Metadata\Type\IType;
25
use POData\Providers\Metadata\Type\StringType;
26
use POData\Providers\Query\QueryResult;
27
use POData\Providers\Query\QueryType;
28
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
29
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode;
30
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\RootProjectionNode;
31
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
32
use POData\UriProcessor\RequestDescription;
33
use POData\UriProcessor\SegmentStack;
34
35
class CynicSerialiser implements IObjectSerialiser
36
{
37
    /**
38
     * The service implementation.
39
     *
40
     * @var IService
41
     */
42
    protected $service;
43
44
    /**
45
     * Request description instance describes OData request the
46
     * the client has submitted and result of the request.
47
     *
48
     * @var RequestDescription
49
     */
50
    protected $request;
51
52
    /**
53
     * Collection of complex type instances used for cycle detection.
54
     *
55
     * @var array
56
     */
57
    protected $complexTypeInstanceCollection;
58
59
    /**
60
     * Absolute service Uri.
61
     *
62
     * @var string
63
     */
64
    protected $absoluteServiceUri;
65
66
    /**
67
     * Absolute service Uri with slash.
68
     *
69
     * @var string
70
     */
71
    protected $absoluteServiceUriWithSlash;
72
73
    /**
74
     * Holds reference to segment stack being processed.
75
     *
76
     * @var SegmentStack
77
     */
78
    protected $stack;
79
80
    /**
81
     * Lightweight stack tracking for recursive descent fill.
82
     */
83
    private $lightStack = [];
84
85
    /*
86
     * Update time to insert into ODataEntry/ODataFeed fields
87
     * @var Carbon;
88
     */
89
    private $updated;
90
91
    /*
92
     * Has base URI already been written out during serialisation?
93
     * @var bool;
94
     */
95
    private $isBaseWritten = false;
96
97
    /**
98
     * @param IService                $service Reference to the data service instance
99
     * @param RequestDescription|null $request Type instance describing the client submitted request
100
     */
101
    public function __construct(IService $service, RequestDescription $request = null)
102
    {
103
        $this->service = $service;
104
        $this->request = $request;
105
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
106
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
107
        $this->stack = new SegmentStack($request);
108
        $this->complexTypeInstanceCollection = [];
109
        $this->updated = Carbon::now();
110
    }
111
112
    /**
113
     * Write a top level entry resource.
114
     *
115
     * @param QueryResult $entryObject Results property contains reference to the entry object to be written
116
     *
117
     * @return ODataEntry|null
118
     */
119
    public function writeTopLevelElement(QueryResult $entryObject)
120
    {
121
        if (!isset($entryObject->results)) {
122
            array_pop($this->lightStack);
123
            return null;
124
        }
125
126
        assert(is_object($entryObject->results));
127
        $this->loadStackIfEmpty();
128
129
        $baseURI = $this->isBaseWritten ? null : $this->absoluteServiceUriWithSlash;
130
        $this->isBaseWritten = true;
131
132
        $stackCount = count($this->lightStack);
133
        $topOfStack = $this->lightStack[$stackCount-1];
134
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack['type']);
135
        assert($resourceType instanceof ResourceType, get_class($resourceType));
136
        $rawProp = $resourceType->getAllProperties();
137
        $relProp = [];
138
        $nonRelProp = [];
139
        $last = end($this->lightStack);
140
        $projNodes = ($last['type'] == $last['property']) ? $this->getProjectionNodes() : null;
141
142
        foreach ($rawProp as $prop) {
143
            $propName = $prop->getName();
144
            if ($prop->getResourceType() instanceof ResourceEntityType) {
145
                $relProp[$propName] = $prop;
146
            } else {
147
                $nonRelProp[$propName] = $prop;
148
            }
149
        }
150
        $rawCount = count($rawProp);
151
        $relCount = count($relProp);
152
        $nonRelCount = count($nonRelProp);
153
        assert(
154
            $rawCount == $relCount + $nonRelCount,
155
            'Raw property count '.$rawCount.', does not equal sum of relProp count, '.$relCount
156
            .', and nonRelPropCount,'.$nonRelCount
157
        );
158
159
        // now mask off against projNodes
160
        if (null !== $projNodes) {
161
            $keys = [];
162
            foreach ($projNodes as $node) {
163
                $keys[$node->getPropertyName()] = '';
164
            }
165
166
            $relProp = array_intersect_key($relProp, $keys);
167
            $nonRelProp = array_intersect_key($nonRelProp, $keys);
168
        }
169
170
        $resourceSet = $resourceType->getCustomState();
171
        assert($resourceSet instanceof ResourceSet);
172
        $title = $resourceType->getName();
173
        $type = $resourceType->getFullName();
174
175
        $relativeUri = $this->getEntryInstanceKey(
176
            $entryObject->results,
177
            $resourceType,
178
            $resourceSet->getName()
179
        );
180
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
181
182
        list($mediaLink, $mediaLinks) = $this->writeMediaData(
183
            $entryObject->results,
184
            $type,
185
            $relativeUri,
186
            $resourceType
187
        );
188
189
        $propertyContent = $this->writeProperties($entryObject->results, $nonRelProp);
190
191
        $links = [];
192
        foreach ($relProp as $prop) {
193
            $nuLink = new ODataLink();
194
            $propKind = $prop->getKind();
195
196
            assert(
197
                ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
198
                || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind,
199
                '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
200
                .' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE'
201
            );
202
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
203
            $propType = 'application/atom+xml;type='.$propTail;
204
            $propName = $prop->getName();
205
            $nuLink->title = $propName;
206
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
207
            $nuLink->url = $relativeUri . '/' . $propName;
208
            $nuLink->type = $propType;
209
210
            $navProp = new ODataNavigationPropertyInfo($prop, $this->shouldExpandSegment($propName));
211
            if ($navProp->expanded) {
212
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
213
            }
214
215
            $links[] = $nuLink;
216
        }
217
218
        $odata = new ODataEntry();
219
        $odata->resourceSetName = $resourceSet->getName();
220
        $odata->id = $absoluteUri;
221
        $odata->title = new ODataTitle($title);
222
        $odata->type = new ODataCategory($type);
223
        $odata->propertyContent = $propertyContent;
224
        $odata->isMediaLinkEntry = true === $resourceType->isMediaLinkEntry() ? true : null;
225
        $odata->editLink = new ODataLink();
226
        $odata->editLink->url = $relativeUri;
227
        $odata->editLink->name = 'edit';
228
        $odata->editLink->title = $title;
229
        $odata->mediaLink = $mediaLink;
230
        $odata->mediaLinks = $mediaLinks;
231
        $odata->links = $links;
232
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
233
        $odata->baseURI = $baseURI;
234
235
        $newCount = count($this->lightStack);
236
        assert(
237
            $newCount == $stackCount,
238
            'Should have ' . $stackCount . 'elements in stack, have ' . $newCount . 'elements'
239
        );
240
        $this->lightStack[$newCount-1]['count']--;
241
        if (0 == $this->lightStack[$newCount-1]['count']) {
242
            array_pop($this->lightStack);
243
        }
244
        return $odata;
245
    }
246
247
    /**
248
     * Write top level feed element.
249
     *
250
     * @param QueryResult &$entryObjects Results property contains array of entry resources to be written
251
     *
252
     * @return ODataFeed
253
     */
254
    public function writeTopLevelElements(QueryResult &$entryObjects)
255
    {
256
        $res = $entryObjects->results;
257
        assert(is_array($res) || $res instanceof Collection, '!is_array($entryObjects->results)');
258
        if (is_array($res) && 0 == count($entryObjects->results)) {
259
            $entryObjects->hasMore = false;
260
        }
261
        if ($res instanceof Collection && 0 == $res->count()) {
262
            $entryObjects->hasMore = false;
263
        }
264
265
        $this->loadStackIfEmpty();
266
        $setName = $this->getRequest()->getTargetResourceSetWrapper()->getName();
267
268
        $title = $this->getRequest()->getContainerName();
269
        $relativeUri = $this->getRequest()->getIdentifier();
270
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
271
272
        $selfLink = new ODataLink();
273
        $selfLink->name = 'self';
274
        $selfLink->title = $title;
275
        $selfLink->url = $relativeUri;
276
277
        $odata = new ODataFeed();
278
        $odata->title = new ODataTitle($title);
279
        $odata->id = $absoluteUri;
280
        $odata->selfLink = $selfLink;
281
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
282
        $odata->baseURI = $this->isBaseWritten ? null : $this->absoluteServiceUriWithSlash;
283
        $this->isBaseWritten = true;
284
285
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
286
            $odata->rowCount = $this->getRequest()->getCountValue();
287
        }
288
        foreach ($res as $entry) {
289
            if (!$entry instanceof QueryResult) {
290
                $query = new QueryResult();
291
                $query->results = $entry;
292
            } else {
293
                $query = $entry;
294
            }
295
            $odata->entries[] = $this->writeTopLevelElement($query);
296
        }
297
298
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
299
        $requestTop = $this->getRequest()->getTopOptionCount();
300
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
301
        $requestTop = (null === $requestTop) ? $pageSize + 1 : $requestTop;
302
303
        if (true === $entryObjects->hasMore && $requestTop > $pageSize) {
304
            $stackSegment = $setName;
305
            $lastObject = end($entryObjects->results);
306
            $segment = $this->getNextLinkUri($lastObject);
307
            $nextLink = new ODataLink();
308
            $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
309
            $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
310
            $odata->nextPageLink = $nextLink;
311
        }
312
313
        return $odata;
314
    }
315
316
    /**
317
     * Write top level url element.
318
     *
319
     * @param QueryResult $entryObject Results property contains the entry resource whose url to be written
320
     *
321
     * @return ODataURL
322
     */
323
    public function writeUrlElement(QueryResult $entryObject)
324
    {
325
        $url = new ODataURL();
326
        if (null !== $entryObject->results) {
327
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
328
            $relativeUri = $this->getEntryInstanceKey(
329
                $entryObject->results,
330
                $currentResourceType,
331
                $this->getCurrentResourceSetWrapper()->getName()
332
            );
333
334
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
335
        }
336
337
        return $url;
338
    }
339
340
    /**
341
     * Write top level url collection.
342
     *
343
     * @param QueryResult $entryObjects Results property contains the array of entry resources whose urls are
344
     *                                  to be written
345
     *
346
     * @return ODataURLCollection
347
     */
348
    public function writeUrlElements(QueryResult $entryObjects)
349
    {
350
        $urls = new ODataURLCollection();
351
        if (!empty($entryObjects->results)) {
352
            $i = 0;
353
            foreach ($entryObjects->results as $entryObject) {
354
                if (!$entryObject instanceof QueryResult) {
355
                    $query = new QueryResult();
356
                    $query->results = $entryObject;
357
                } else {
358
                    $query = $entryObject;
359
                }
360
                $urls->urls[$i] = $this->writeUrlElement($query);
361
                ++$i;
362
            }
363
364
            if ($i > 0 && true === $entryObjects->hasMore) {
365
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
366
                $lastObject = end($entryObjects->results);
367
                $segment = $this->getNextLinkUri($lastObject);
368
                $nextLink = new ODataLink();
369
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
370
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
371
                $nextLink->url = ltrim($nextLink->url, '/');
372
                $urls->nextPageLink = $nextLink;
373
            }
374
        }
375
376
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
377
            $urls->count = $this->getRequest()->getCountValue();
378
        }
379
380
        return $urls;
381
    }
382
383
    /**
384
     * Write top level complex resource.
385
     *
386
     * @param QueryResult  &$complexValue Results property contains the complex object to be written
387
     * @param string       $propertyName  The name of the complex property
388
     * @param ResourceType &$resourceType Describes the type of complex object
389
     *
390
     * @return ODataPropertyContent
391
     */
392
    public function writeTopLevelComplexObject(QueryResult &$complexValue, $propertyName, ResourceType &$resourceType)
393
    {
394
        $result = $complexValue->results;
395
396
        $propertyContent = new ODataPropertyContent();
397
        $odataProperty = new ODataProperty();
398
        $odataProperty->name = $propertyName;
399
        $odataProperty->typeName = $resourceType->getFullName();
400
        if (null !== $result) {
401
            assert(is_object($result), 'Supplied $customObject must be an object');
402
            $internalContent = $this->writeComplexValue($resourceType, $result);
403
            $odataProperty->value = $internalContent;
404
        }
405
406
        $propertyContent->properties[$propertyName] = $odataProperty;
407
408
        return $propertyContent;
409
    }
410
411
    /**
412
     * Write top level bag resource.
413
     *
414
     * @param QueryResult  $bagValue
415
     * @param string       $propertyName  The name of the bag property
416
     * @param ResourceType &$resourceType Describes the type of bag object
417
     *
418
     * @return ODataPropertyContent
419
     */
420
    public function writeTopLevelBagObject(QueryResult &$bagValue, $propertyName, ResourceType &$resourceType)
421
    {
422
        $result = $bagValue->results;
423
424
        $propertyContent = new ODataPropertyContent();
425
        $odataProperty = new ODataProperty();
426
        $odataProperty->name = $propertyName;
427
        $odataProperty->typeName = 'Collection('.$resourceType->getFullName().')';
428
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
429
430
        $propertyContent->properties[$propertyName] = $odataProperty;
431
        return $propertyContent;
432
    }
433
434
    /**
435
     * Write top level primitive value.
436
     *
437
     * @param QueryResult      &$primitiveValue   Results property contains the primitive value to be written
438
     * @param ResourceProperty &$resourceProperty Resource property describing the primitive property to be written
439
     *
440
     * @return ODataPropertyContent
441
     */
442
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
443
    {
444
        assert(null !== $resourceProperty, 'Resource property must not be null');
445
        $result = new ODataPropertyContent();
446
        $property = new ODataProperty();
447
        $property->name = $resourceProperty->getName();
448
449
        $iType = $resourceProperty->getInstanceType();
450
        assert($iType instanceof IType, get_class($iType));
451
        $property->typeName = $iType->getFullTypeName();
452
        if (null !== $primitiveValue->results) {
453
            $rType = $resourceProperty->getResourceType()->getInstanceType();
454
            assert($rType instanceof IType, get_class($rType));
455
            $property->value = $this->primitiveToString($rType, $primitiveValue->results);
456
        }
457
458
        $result->properties[$property->name] = $property;
459
        return $result;
460
    }
461
462
    /**
463
     * Gets reference to the request submitted by client.
464
     *
465
     * @return RequestDescription
466
     */
467
    public function getRequest()
468
    {
469
        assert(null !== $this->request, 'Request not yet set');
470
471
        return $this->request;
472
    }
473
474
    /**
475
     * Sets reference to the request submitted by client.
476
     *
477
     * @param  RequestDescription $request
478
     * @return void
479
     */
480
    public function setRequest(RequestDescription $request)
481
    {
482
        $this->request = $request;
483
        $this->stack->setRequest($request);
484
    }
485
486
    /**
487
     * Gets the data service instance.
488
     *
489
     * @return IService
490
     */
491
    public function getService()
492
    {
493
        return $this->service;
494
    }
495
496
    /**
497
     * Sets the data service instance.
498
     *
499
     * @param  IService $service
500
     * @return void
501
     */
502
    public function setService(IService $service)
503
    {
504
        $this->service = $service;
505
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
506
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
507
    }
508
509
    /**
510
     * Gets the segment stack instance.
511
     *
512
     * @return SegmentStack
513
     */
514
    public function getStack()
515
    {
516
        return $this->stack;
517
    }
518
519
    /**
520
     * Get update timestamp.
521
     *
522
     * @return Carbon
523
     */
524
    public function getUpdated()
525
    {
526
        return $this->updated;
527
    }
528
529
    /**
530
     * @param ResourceType $resourceType
531
     * @param $result
532
     * @return ODataBagContent|null
533
     */
534
    protected function writeBagValue(ResourceType &$resourceType, $result)
535
    {
536
        assert(null === $result || is_array($result), 'Bag parameter must be null or array');
537
        $typeKind = $resourceType->getResourceTypeKind();
538
        assert(
539
            ResourceTypeKind::PRIMITIVE() == $typeKind || ResourceTypeKind::COMPLEX() == $typeKind,
540
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
541
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
542
        );
543
        if (null == $result) {
544
            return null;
545
        }
546
        $bag = new ODataBagContent();
547
        foreach ($result as $value) {
548
            if (isset($value)) {
549
                if (ResourceTypeKind::PRIMITIVE() == $typeKind) {
550
                    $instance = $resourceType->getInstanceType();
551
                    assert($instance instanceof IType, get_class($instance));
552
                    $bag->propertyContents[] = $this->primitiveToString($instance, $value);
553
                } elseif (ResourceTypeKind::COMPLEX() == $typeKind) {
554
                    $bag->propertyContents[] = $this->writeComplexValue($resourceType, $value);
555
                }
556
            }
557
        }
558
        return $bag;
559
    }
560
561
    /**
562
     * @param  ResourceType         $resourceType
563
     * @param  object               $result
564
     * @param  string|null          $propertyName
565
     * @return ODataPropertyContent
566
     */
567
    protected function writeComplexValue(ResourceType &$resourceType, &$result, $propertyName = null)
568
    {
569
        assert(is_object($result), 'Supplied $customObject must be an object');
570
571
        $count = count($this->complexTypeInstanceCollection);
572
        for ($i = 0; $i < $count; ++$i) {
573
            if ($this->complexTypeInstanceCollection[$i] === $result) {
574
                throw new InvalidOperationException(
575
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
576
                );
577
            }
578
        }
579
580
        $this->complexTypeInstanceCollection[$count] = &$result;
581
582
        $internalContent = new ODataPropertyContent();
583
        $resourceProperties = $resourceType->getAllProperties();
584
        // first up, handle primitive properties
585
        foreach ($resourceProperties as $prop) {
586
            $resourceKind = $prop->getKind();
587
            $propName = $prop->getName();
588
            $internalProperty = new ODataProperty();
589
            $internalProperty->name = $propName;
590
            $raw = $result->$propName;
591
            if (static::isMatchPrimitive($resourceKind)) {
592
                $iType = $prop->getInstanceType();
593
                assert($iType instanceof IType, get_class($iType));
594
                $internalProperty->typeName = $iType->getFullTypeName();
595
596
                $rType = $prop->getResourceType()->getInstanceType();
597
                assert($rType instanceof IType, get_class($rType));
598
                if (null !== $raw) {
599
                    $internalProperty->value = $this->primitiveToString($rType, $raw);
600
                }
601
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
602
                $rType = $prop->getResourceType();
603
                $internalProperty->typeName = $rType->getFullName();
604
                if (null !== $raw) {
605
                    $internalProperty->value = $this->writeComplexValue($rType, $raw, $propName);
606
                }
607
            }
608
            $internalContent->properties[$propName] = $internalProperty;
609
        }
610
611
        unset($this->complexTypeInstanceCollection[$count]);
612
        return $internalContent;
613
    }
614
615
    /**
616
     * Check whether to expand a navigation property or not.
617
     *
618
     * @param string $navigationPropertyName Name of naviagtion property in question
619
     *
620
     * @return bool True if the given navigation should be expanded, otherwise false
621
     */
622
    protected function shouldExpandSegment($navigationPropertyName)
623
    {
624
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
625
        if (null === $expandedProjectionNode) {
626
            return false;
627
        }
628
629
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
630
631
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
632
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
633
    }
634
635
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
636
    {
637
        assert(is_object($entityInstance));
638
        $typeName = $resourceType->getName();
639
        $keyProperties = $resourceType->getKeyProperties();
640
        assert(0 != count($keyProperties), 'count($keyProperties) == 0');
641
        $keyString = $containerName . '(';
642
        $comma = null;
643
        foreach ($keyProperties as $keyName => $resourceProperty) {
644
            $keyType = $resourceProperty->getInstanceType();
645
            assert($keyType instanceof IType, '$keyType not instanceof IType');
646
            $keyName = $resourceProperty->getName();
647
            $keyValue = $entityInstance->$keyName;
648
            if (!isset($keyValue)) {
649
                $msg = Messages::badQueryNullKeysAreNotSupported($typeName, $keyName);
650
                throw ODataException::createInternalServerError($msg);
651
            }
652
653
            $keyValue = $keyType->convertToOData($keyValue);
654
            $keyString .= $comma . $keyName . '=' . $keyValue;
655
            $comma = ',';
656
        }
657
658
        $keyString .= ')';
659
660
        return $keyString;
661
    }
662
663
    /**
664
     * Resource set wrapper for the resource being serialized.
665
     *
666
     * @return ResourceSetWrapper
667
     */
668 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...
669
    {
670
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
671
        $count = count($segmentWrappers);
672
673
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
674
    }
675
676
    /**
677
     * Wheter next link is needed for the current resource set (feed)
678
     * being serialized.
679
     *
680
     * @param int $resultSetCount Number of entries in the current
681
     *                            resource set
682
     *
683
     * @return bool true if the feed must have a next page link
684
     */
685
    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...
686
    {
687
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
688
        $recursionLevel = count($this->getStack()->getSegmentNames());
689
        $pageSize = $currentResourceSet->getResourceSetPageSize();
690
691
        if (1 == $recursionLevel) {
692
            //presence of $top option affect next link for root container
693
            $topValueCount = $this->getRequest()->getTopOptionCount();
694
            if (null !== $topValueCount && ($topValueCount <= $pageSize)) {
695
                return false;
696
            }
697
        }
698
        return $resultSetCount == $pageSize;
699
    }
700
701
    /**
702
     * Get next page link from the given entity instance.
703
     *
704
     * @param mixed &$lastObject Last object serialized to be
705
     *                           used for generating $skiptoken
706
     *
707
     * @return string for the link for next page
708
     */
709
    protected function getNextLinkUri(&$lastObject)
710
    {
711
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
712
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
713
        assert(null !== $internalOrderByInfo);
714
        assert(is_object($internalOrderByInfo));
715
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
716
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
717
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
718
719
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
720
        assert(null !== $skipToken, '!is_null($skipToken)');
721
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
722
        $skipToken = '?'.$queryParameterString.$token.$skipToken;
723
724
        return $skipToken;
725
    }
726
727
    /**
728
     * @param $entryObject
729
     * @param $type
730
     * @param $relativeUri
731
     * @param $resourceType
732
     * @return array<ODataMediaLink|null|array>
733
     */
734
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
735
    {
736
        $context = $this->getService()->getOperationContext();
737
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
738
        assert(null != $streamProviderWrapper, 'Retrieved stream provider must not be null');
739
740
        $mediaLink = null;
741
        if ($resourceType->isMediaLinkEntry()) {
742
            $eTag = $streamProviderWrapper->getStreamETag2($entryObject, null, $context);
743
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag, 'edit-media');
744
        }
745
        $mediaLinks = [];
746
        if ($resourceType->hasNamedStream()) {
747
            $namedStreams = $resourceType->getAllNamedStreams();
748
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
749
                $readUri = $streamProviderWrapper->getReadStreamUri2(
750
                    $entryObject,
751
                    $resourceStreamInfo,
752
                    $context,
753
                    $relativeUri
754
                );
755
                $mediaContentType = $streamProviderWrapper->getStreamContentType2(
756
                    $entryObject,
757
                    $resourceStreamInfo,
758
                    $context
759
                );
760
                $eTag = $streamProviderWrapper->getStreamETag2(
761
                    $entryObject,
762
                    $resourceStreamInfo,
763
                    $context
764
                );
765
766
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
767
                $mediaLinks[] = $nuLink;
768
            }
769
        }
770
        return [$mediaLink, $mediaLinks];
771
    }
772
773
    /**
774
     * @param $entryObject
775
     * @param $prop
776
     * @param $nuLink
777
     * @param $propKind
778
     * @param $propName
779
     */
780
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
781
    {
782
        $nextName = $prop->getResourceType()->getName();
783
        $nuLink->isExpanded = true;
784
        $value = $entryObject->results->$propName;
785
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind || !is_object($value);
786
        $nuLink->isCollection = $isCollection;
787
        $nullResult = null === $value;
788
        $resultCount = $nullResult ? 0 : count($value);
789
790
        if (0 < $resultCount) {
791
            $result = new QueryResult();
792
            $result->results = $value;
793
            if (!$nullResult) {
794
                $newStackLine = ['type' => $nextName, 'property' => $propName, 'count' => $resultCount];
795
                array_push($this->lightStack, $newStackLine);
796
                if (isset($value)) {
797
                    if (!$isCollection) {
798
                        $nuLink->type = 'application/atom+xml;type=entry';
799
                        $expandedResult = $this->writeTopLevelElement($result);
800
                    } else {
801
                        $nuLink->type = 'application/atom+xml;type=feed';
802
                        $expandedResult = $this->writeTopLevelElements($result);
803
                    }
804
                    $nuLink->expandedResult = $expandedResult;
805
                }
806
            }
807
        }
808
        if (!isset($nuLink->expandedResult)) {
809
            $nuLink->isCollection = null;
810
            $nuLink->isExpanded = null;
811
        } else {
812
            if (isset($nuLink->expandedResult->selfLink)) {
813
                $nuLink->expandedResult->selfLink->title = $propName;
814
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
815
                $nuLink->expandedResult->title = new ODataTitle($propName);
816
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
817
            }
818
        }
819
    }
820
821
    /**
822
     * Gets collection of projection nodes under the current node.
823
     *
824
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes describing projections for the current
825
     *                                                        segment, If this method returns null it means no
826
     *                                                        projections are to be applied and the entire resource for
827
     *                                                        the current segment should be serialized, If it returns
828
     *                                                        non-null only the properties described by the returned
829
     *                                                        projection segments should be serialized
830
     */
831
    protected function getProjectionNodes()
832
    {
833
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
834
        if (null === $expandedProjectionNode || $expandedProjectionNode->canSelectAllProperties()) {
835
            return null;
836
        }
837
838
        return $expandedProjectionNode->getChildNodes();
839
    }
840
841
    /**
842
     * Find a 'ExpandedProjectionNode' instance in the projection tree
843
     * which describes the current segment.
844
     *
845
     * @return null|RootProjectionNode|ExpandedProjectionNode
846
     */
847 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...
848
    {
849
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
850
        if (null === $expandedProjectionNode) {
851
            return null;
852
        } else {
853
            $segmentNames = $this->getStack()->getSegmentNames();
854
            $depth = count($segmentNames);
855
            // $depth == 1 means serialization of root entry
856
            //(the resource identified by resource path) is going on,
857
            //so control won't get into the below for loop.
858
            //we will directly return the root node,
859
            //which is 'ExpandedProjectionNode'
860
            // for resource identified by resource path.
861
            if (0 != $depth) {
862
                for ($i = 1; $i < $depth; ++$i) {
863
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]);
864
                    assert(null !== $expandedProjectionNode, 'is_null($expandedProjectionNode)');
865
                    assert(
866
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
867
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
868
                    );
869
                }
870
            }
871
        }
872
873
        return $expandedProjectionNode;
874
    }
875
876
    /**
877
     * Builds the string corresponding to query parameters for top level results
878
     * (result set identified by the resource path) to be put in next page link.
879
     *
880
     * @return string|null string representing the query parameters in the URI
881
     *                     query parameter format, NULL if there
882
     *                     is no query parameters
883
     *                     required for the next link of top level result set
884
     */
885
    protected function getNextPageLinkQueryParametersForRootResourceSet()
886
    {
887
        $queryParameterString = null;
888
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
889
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
890
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
891
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
892
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
893
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
894
            if (null !== $value) {
895
                if (null !== $queryParameterString) {
896
                    $queryParameterString = $queryParameterString . '&';
897
                }
898
899
                $queryParameterString .= $queryOption . '=' . $value;
900
            }
901
        }
902
903
        $topCountValue = $this->getRequest()->getTopOptionCount();
904
        if (null !== $topCountValue) {
905
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
906
            if (0 < $remainingCount) {
907
                if (null !== $queryParameterString) {
908
                    $queryParameterString .= '&';
909
                }
910
911
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
912
            }
913
        }
914
915
        if (null !== $queryParameterString) {
916
            $queryParameterString .= '&';
917
        }
918
919
        return $queryParameterString;
920
    }
921
922
    /**
923
     * @param $entryObject
924
     * @param $nonRelProp
925
     * @return ODataPropertyContent
926
     */
927
    private function writeProperties($entryObject, $nonRelProp)
928
    {
929
        $propertyContent = new ODataPropertyContent();
930
        foreach ($nonRelProp as $corn => $flake) {
931
            $resource = $nonRelProp[$corn]->getResourceType();
932
            if ($resource instanceof ResourceEntityType) {
933
                continue;
934
            }
935
            $result = $entryObject->$corn;
936
            $isBag = $flake->isKindOf(ResourcePropertyKind::BAG);
937
            $typePrepend = $isBag ? 'Collection(' : '';
938
            $typeAppend = $isBag ? ')' : '';
939
            $nonNull = null !== $result;
940
            $subProp = new ODataProperty();
941
            $subProp->name = strval($corn);
942
            $subProp->typeName = $typePrepend . $resource->getFullName() . $typeAppend;
943
944
            if ($nonNull && is_array($result)) {
945
                $subProp->value = $this->writeBagValue($resource, $result);
946 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...
947
                $rType = $resource->getInstanceType();
948
                assert($rType instanceof IType, get_class($rType));
949
                $subProp->value = $this->primitiveToString($rType, $result);
950
            } elseif ($resource instanceof ResourceComplexType && $nonNull) {
951
                $subProp->value = $this->writeComplexValue($resource, $result, $flake->getName());
952
            }
953
            $propertyContent->properties[$corn] = $subProp;
954
        }
955
        return $propertyContent;
956
    }
957
958
    /**
959
     * @return void
960
     */
961
    private function loadStackIfEmpty()
962
    {
963
        if (0 == count($this->lightStack)) {
964
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
965
            array_push($this->lightStack, ['type' => $typeName, 'property' => $typeName, 'count' => 1]);
966
        }
967
    }
968
969
    /**
970
     * Convert the given primitive value to string.
971
     * Note: This method will not handle null primitive value.
972
     *
973
     * @param IType &$primitiveResourceType Type of the primitive property
974
     *                                      whose value need to be converted
975
     * @param mixed $primitiveValue         Primitive value to convert
976
     *
977
     * @return string
978
     */
979
    private function primitiveToString(IType &$type, $primitiveValue)
980
    {
981
        if ($type instanceof Boolean) {
982
            $stringValue = (true === $primitiveValue) ? 'true' : 'false';
983
        } elseif ($type instanceof Binary) {
984
            $stringValue = base64_encode($primitiveValue);
985
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
986
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
987
        } elseif ($type instanceof StringType) {
988
            $stringValue = utf8_encode($primitiveValue);
989
        } else {
990
            $stringValue = strval($primitiveValue);
991
        }
992
993
        return $stringValue;
994
    }
995
996
    public static function isMatchPrimitive($resourceKind)
997
    {
998
        if (16 > $resourceKind) {
999
            return false;
1000
        }
1001
        if (28 < $resourceKind) {
1002
            return false;
1003
        }
1004
        return 0 == ($resourceKind % 4);
1005
    }
1006
}
1007