Test Setup Failed
Push — master ( a7c84c...018894 )
by Alex
05:17
created

CynicSerialiser::writeTopLevelElement()   F

Complexity

Conditions 13
Paths 241

Size

Total Lines 123
Code Lines 92

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 123
rs 3.9333
cc 13
eloc 92
nc 241
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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