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

IronicSerialiser::getProjectionNodes()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

Loading history...
299
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
300
                $lastObject = end($entryObjects->results);
301
                $segment = $this->getNextLinkUri($lastObject, $this->getRequest()->getRequestUrl()->getUrlAsString());
302
                $nextLink = new ODataLink();
303
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
304
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
305
                $urls->nextPageLink = $nextLink;
306
            }
307
        }
308
309
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
310
            $urls->count = $this->getRequest()->getCountValue();
311
        }
312
313
        return $urls;
314
    }
315
316
    /**
317
     * Write top level complex resource.
318
     *
319
     * @param mixed &$complexValue The complex object to be
320
     *                                    written
321
     * @param string $propertyName The name of the
322
     *                                    complex property
323
     * @param ResourceType &$resourceType Describes the type of
324
     *                                    complex object
325
     *
326
     * @return ODataPropertyContent
327
     */
328
    public function writeTopLevelComplexObject(QueryResult &$complexValue, $propertyName, ResourceType &$resourceType)
329
    {
330
        $result = $complexValue->results;
331
332
        $propertyContent = new ODataPropertyContent();
333
        $odataProperty = new ODataProperty();
334
        $odataProperty->name = $propertyName;
335
        $odataProperty->typeName = $resourceType->getFullName();
336
        if (null != $result) {
337
            $internalContent = new ODataPropertyContent();
338
            $resourceProperties = $resourceType->getAllProperties();
339
            // first up, handle primitive properties
340
            foreach ($resourceProperties as $prop) {
341
                $propName = $prop->getName();
342
                $internalProperty = new ODataProperty();
343
                $internalProperty->name = $propName;
344
                $iType = $prop->getInstanceType();
345
                $internalProperty->typeName = $iType->getFullTypeName();
0 ignored issues
show
Bug introduced by
The method getFullTypeName does only exist in POData\Providers\Metadata\Type\IType, but not in ReflectionClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
346
347
                $rType = $prop->getResourceType()->getInstanceType();
348
                $internalProperty->value = $this->primitiveToString($rType, $result->$propName);
0 ignored issues
show
Bug introduced by
It seems like $rType defined by $prop->getResourceType()->getInstanceType() on line 347 can also be of type object<ReflectionClass> or string; however, AlgoWeb\PODataLaravel\Se...er::primitiveToString() does only seem to accept object<POData\Providers\Metadata\Type\IType>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
349
                $internalContent->properties[] = $internalProperty;
350
            }
351
352
353
            $odataProperty->value = $internalContent;
354
        }
355
356
        $propertyContent->properties[] = $odataProperty;
357
358
        return $propertyContent;
359
    }
360
361
    /**
362
     * Write top level bag resource.
363
     *
364
     * @param mixed &$BagValue The bag object to be
365
     *                                    written
366
     * @param string $propertyName The name of the
367
     *                                    bag property
368
     * @param ResourceType &$resourceType Describes the type of
369
     *                                    bag object
370
     * @return ODataPropertyContent
371
     */
372
    public function writeTopLevelBagObject(QueryResult &$BagValue, $propertyName, ResourceType &$resourceType)
373
    {
374
        $result = $BagValue->results;
375
376
        $propertyContent = new ODataPropertyContent();
377
        $odataProperty = new ODataProperty();
378
        $odataProperty->name = $propertyName;
379
        $odataProperty->typeName = 'Collection('.$resourceType->getFullName().')';
380
        if (null != $result) {
381
            $result = array_diff($result, [null]);
382
            $bag = new ODataBagContent();
383
            $instance = $resourceType->getInstanceType();
384
            foreach ($result as $value) {
385
                $bag->propertyContents[] = $this->primitiveToString($instance, $value);
0 ignored issues
show
Bug introduced by
It seems like $instance defined by $resourceType->getInstanceType() on line 383 can also be of type object<ReflectionClass> or string; however, AlgoWeb\PODataLaravel\Se...er::primitiveToString() does only seem to accept object<POData\Providers\Metadata\Type\IType>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
386
            }
387
388
            $odataProperty->value = $bag;
389
        }
390
391
        $propertyContent->properties[] = $odataProperty;
392
        return $propertyContent;
393
    }
394
395
    /**
396
     * Write top level primitive value.
397
     *
398
     * @param mixed &$primitiveValue The primitve value to be
399
     *                                            written
400
     * @param ResourceProperty &$resourceProperty Resource property
401
     *                                            describing the
402
     *                                            primitive property
403
     *                                            to be written
404
     * @return ODataPropertyContent
405
     */
406
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
407
    {
408
        assert(null != $resourceProperty, "Resource property must not be null");
409
        $propertyContent = new ODataPropertyContent();
410
411
        $odataProperty = new ODataProperty();
412
        $odataProperty->name = $resourceProperty->getName();
413
        $odataProperty->typeName = $resourceProperty->getInstanceType()->getFullTypeName();
0 ignored issues
show
Bug introduced by
The method getFullTypeName does only exist in POData\Providers\Metadata\Type\IType, but not in ReflectionClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
414
        if (null == $primitiveValue->results) {
415
            $odataProperty->value = null;
416
        } else {
417
            $rType = $resourceProperty->getResourceType()->getInstanceType();
418
            $odataProperty->value = $this->primitiveToString($rType, $primitiveValue->results);
0 ignored issues
show
Bug introduced by
It seems like $rType defined by $resourceProperty->getRe...pe()->getInstanceType() on line 417 can also be of type object<ReflectionClass> or string; however, AlgoWeb\PODataLaravel\Se...er::primitiveToString() does only seem to accept object<POData\Providers\Metadata\Type\IType>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
419
        }
420
421
        $propertyContent->properties[] = $odataProperty;
422
423
        return $propertyContent;
424
    }
425
426
    /**
427
     * Gets reference to the request submitted by client.
428
     *
429
     * @return RequestDescription
430
     */
431
    public function getRequest()
432
    {
433
        assert(null != $this->request, 'Request not yet set');
434
435
        return $this->request;
436
    }
437
438
    /**
439
     * Sets reference to the request submitted by client.
440
     *
441
     * @param RequestDescription $request
442
     */
443
    public function setRequest(RequestDescription $request)
444
    {
445
        $this->request = $request;
446
        $this->stack->setRequest($request);
447
    }
448
449
    /**
450
     * Gets the data service instance.
451
     *
452
     * @return IService
453
     */
454
    public function getService()
455
    {
456
        return $this->service;
457
    }
458
459
    /**
460
     * Gets the segment stack instance.
461
     *
462
     * @return SegmentStack
463
     */
464
    public function getStack()
465
    {
466
        return $this->stack;
467
    }
468
469
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
470
    {
471
        $typeName = $resourceType->getName();
472
        $keyProperties = $resourceType->getKeyProperties();
473
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
474
        $keyString = $containerName . '(';
475
        $comma = null;
476
        foreach ($keyProperties as $keyName => $resourceProperty) {
477
            $keyType = $resourceProperty->getInstanceType();
478
            assert($keyType instanceof IType, '$keyType not instanceof IType');
479
            $keyName = $resourceProperty->getName();
480
            $keyValue = $entityInstance->$keyName;
481
            if (!isset($keyValue)) {
482
                throw ODataException::createInternalServerError(
483
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
484
                );
485
            }
486
487
            $keyValue = $keyType->convertToOData($keyValue);
488
            $keyString .= $comma . $keyName . '=' . $keyValue;
489
            $comma = ',';
490
        }
491
492
        $keyString .= ')';
493
494
        return $keyString;
495
    }
496
497
    /**
498
     * @param $entryObject
499
     * @param $type
500
     * @param $relativeUri
501
     * @param $resourceType
502
     * @return array
503
     */
504
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
505
    {
506
        $context = $this->getService()->getOperationContext();
507
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
508
        assert(null != $streamProviderWrapper, "Retrieved stream provider must not be null");
509
510
        $mediaLink = null;
511
        if ($resourceType->isMediaLinkEntry()) {
512
            $eTag = $streamProviderWrapper->getStreamETag2($entryObject, null, $context);
0 ignored issues
show
Bug introduced by
The method getStreamETag2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getStreamETag()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
513
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag);
514
        }
515
        $mediaLinks = [];
516
        if ($resourceType->hasNamedStream()) {
517
            $namedStreams = $resourceType->getAllNamedStreams();
518
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
519
                $readUri = $streamProviderWrapper->getReadStreamUri2(
0 ignored issues
show
Bug introduced by
The method getReadStreamUri2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getReadStream()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
520
                    $entryObject,
521
                    $resourceStreamInfo,
522
                    $context,
523
                    $relativeUri
524
                );
525
                $mediaContentType = $streamProviderWrapper->getStreamContentType2(
0 ignored issues
show
Bug introduced by
The method getStreamContentType2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getStreamContentType()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
526
                    $entryObject,
527
                    $resourceStreamInfo,
528
                    $context
529
                );
530
                $eTag = $streamProviderWrapper->getStreamETag2(
0 ignored issues
show
Bug introduced by
The method getStreamETag2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getStreamETag()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
531
                    $entryObject,
532
                    $resourceStreamInfo,
533
                    $context
534
                );
535
536
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
537
                $mediaLinks[] = $nuLink;
538
            }
539
        }
540
        return [$mediaLink, $mediaLinks];
541
    }
542
543
    /**
544
     * Gets collection of projection nodes under the current node.
545
     *
546
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes
547
     *                                                        describing projections for the current segment, If this method returns
548
     *                                                        null it means no projections are to be applied and the entire resource
549
     *                                                        for the current segment should be serialized, If it returns non-null
550
     *                                                        only the properties described by the returned projection segments should
551
     *                                                        be serialized
552
     */
553
    protected function getProjectionNodes()
554
    {
555
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
556
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
557
            return null;
558
        }
559
560
        return $expandedProjectionNode->getChildNodes();
561
    }
562
563
    /**
564
     * Find a 'ExpandedProjectionNode' instance in the projection tree
565
     * which describes the current segment.
566
     *
567
     * @return ExpandedProjectionNode|null
568
     */
569
    protected function getCurrentExpandedProjectionNode()
570
    {
571
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
572
        if (is_null($expandedProjectionNode)) {
573
            return null;
574
        } else {
575
            $segmentNames = $this->getStack()->getSegmentNames();
576
            $depth = count($segmentNames);
577
            // $depth == 1 means serialization of root entry
578
            //(the resource identified by resource path) is going on,
579
            //so control won't get into the below for loop.
580
            //we will directly return the root node,
581
            //which is 'ExpandedProjectionNode'
582
            // for resource identified by resource path.
583
            if (0 != $depth) {
584
                for ($i = 1; $i < $depth; ++$i) {
585
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]);
586
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
587
                    assert(
588
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
589
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
590
                    );
591
                }
592
            }
593
        }
594
595
        return $expandedProjectionNode;
596
    }
597
598
    /**
599
     * Check whether to expand a navigation property or not.
600
     *
601
     * @param string $navigationPropertyName Name of naviagtion property in question
602
     *
603
     * @return bool True if the given navigation should be
604
     *              explanded otherwise false
605
     */
606
    protected function shouldExpandSegment($navigationPropertyName)
607
    {
608
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
609
        if (is_null($expandedProjectionNode)) {
610
            return false;
611
        }
612
613
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
614
615
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
616
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
617
    }
618
619
    /**
620
     * Wheter next link is needed for the current resource set (feed)
621
     * being serialized.
622
     *
623
     * @param int $resultSetCount Number of entries in the current
624
     *                            resource set
625
     *
626
     * @return bool true if the feed must have a next page link
627
     */
628
    protected function needNextPageLink($resultSetCount)
629
    {
630
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
631
        $recursionLevel = count($this->getStack()->getSegmentNames());
632
        $pageSize = $currentResourceSet->getResourceSetPageSize();
633
634
        if (1 == $recursionLevel) {
635
            //presence of $top option affect next link for root container
636
            $topValueCount = $this->getRequest()->getTopOptionCount();
637
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
638
                return false;
639
            }
640
        }
641
        return $resultSetCount == $pageSize;
642
    }
643
644
    /**
645
     * Resource set wrapper for the resource being serialized.
646
     *
647
     * @return ResourceSetWrapper
648
     */
649
    protected function getCurrentResourceSetWrapper()
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
     * Get next page link from the given entity instance.
659
     *
660
     * @param mixed  &$lastObject Last object serialized to be
661
     *                            used for generating $skiptoken
662
     * @param string $absoluteUri Absolute response URI
663
     *
664
     * @return string for the link for next page
665
     */
666
    protected function getNextLinkUri(&$lastObject, $absoluteUri)
0 ignored issues
show
Unused Code introduced by
The parameter $absoluteUri is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
667
    {
668
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
669
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
670
        assert(null != $internalOrderByInfo);
671
        assert(is_object($internalOrderByInfo));
672
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
673
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
674
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
675
676
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
677
        assert(!is_null($skipToken), '!is_null($skipToken)');
678
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
679
        $skipToken = '?'.$queryParameterString.$token.$skipToken;
680
681
        return $skipToken;
682
    }
683
684
    /**
685
     * Builds the string corresponding to query parameters for top level results
686
     * (result set identified by the resource path) to be put in next page link.
687
     *
688
     * @return string|null string representing the query parameters in the URI
689
     *                     query parameter format, NULL if there
690
     *                     is no query parameters
691
     *                     required for the next link of top level result set
692
     */
693
    protected function getNextPageLinkQueryParametersForRootResourceSet()
694
    {
695
        $queryParameterString = null;
696
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
697
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
698
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
699
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
700
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
701
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
702
            if (!is_null($value)) {
703
                if (!is_null($queryParameterString)) {
704
                    $queryParameterString = $queryParameterString . '&';
705
                }
706
707
                $queryParameterString .= $queryOption . '=' . $value;
708
            }
709
        }
710
711
        $topCountValue = $this->getRequest()->getTopOptionCount();
712
        if (!is_null($topCountValue)) {
713
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
714
            if (0 < $remainingCount) {
715
                if (!is_null($queryParameterString)) {
716
                    $queryParameterString .= '&';
717
                }
718
719
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
720
            }
721
        }
722
723
        if (!is_null($queryParameterString)) {
724
            $queryParameterString .= '&';
725
        }
726
727
        return $queryParameterString;
728
    }
729
730
    private function loadStackIfEmpty()
731
    {
732
        if (0 == count($this->lightStack)) {
733
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
734
            array_push($this->lightStack, [$typeName, $typeName]);
735
        }
736
    }
737
738
    /**
739
     * Convert the given primitive value to string.
740
     * Note: This method will not handle null primitive value.
741
     *
742
     * @param IType &$primitiveResourceType        Type of the primitive property
743
     *                                             whose value need to be converted
744
     * @param mixed        $primitiveValue         Primitive value to convert
745
     *
746
     * @return string
747
     */
748
    private function primitiveToString(IType &$type, $primitiveValue)
749
    {
750
        if ($type instanceof Boolean) {
751
            $stringValue = (true === $primitiveValue) ? 'true' : 'false';
752
        } elseif ($type instanceof Binary) {
753
            $stringValue = base64_encode($primitiveValue);
754
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
755
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
756
        } elseif ($type instanceof StringType) {
757
            $stringValue = utf8_encode($primitiveValue);
758
        } else {
759
            $stringValue = strval($primitiveValue);
760
        }
761
762
        return $stringValue;
763
    }
764
765
    /**
766
     * @param $entryObject
767
     * @param $nonRelProp
768
     * @return ODataPropertyContent
769
     */
770
    private function writePrimitiveProperties($entryObject, $nonRelProp)
771
    {
772
        $propertyContent = new ODataPropertyContent();
773
        $cereal = $this->modelSerialiser->bulkSerialise($entryObject);
774
        foreach ($cereal as $corn => $flake) {
775
            if (!array_key_exists($corn, $nonRelProp)) {
776
                continue;
777
            }
778
            $corn = strval($corn);
779
            $rType = $nonRelProp[$corn]->getResourceType()->getInstanceType();
780
            $subProp = new ODataProperty();
781
            $subProp->name = $corn;
782
            $subProp->value = isset($flake) ? $this->primitiveToString($rType, $flake) : null;
783
            $subProp->typeName = $nonRelProp[$corn]->getResourceType()->getFullName();
784
            $propertyContent->properties[] = $subProp;
785
        }
786
        return $propertyContent;
787
    }
788
789
    /**
790
     * @param $entryObject
791
     * @param $prop
792
     * @param $nuLink
793
     * @param $propKind
794
     * @param $propName
795
     */
796
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
797
    {
798
        $nextName = $prop->getResourceType()->getName();
799
        $nuLink->isExpanded = true;
800
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
801
        $nuLink->isCollection = $isCollection;
802
        $value = $entryObject->results->$propName;
803
        $result = new QueryResult();
804
        $result->results = $value;
805
        array_push($this->lightStack, [$nextName, $propName]);
806
        if (!$isCollection) {
807
            $expandedResult = $this->writeTopLevelElement($result);
808
        } else {
809
            $expandedResult = $this->writeTopLevelElements($result);
810
        }
811
        $nuLink->expandedResult = $expandedResult;
812
        if (!isset($nuLink->expandedResult)) {
813
            $nuLink->isCollection = null;
814
            $nuLink->isExpanded = null;
815
        } else {
816
            if (isset($nuLink->expandedResult->selfLink)) {
817
                $nuLink->expandedResult->selfLink->title = $propName;
818
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
819
                $nuLink->expandedResult->title = $propName;
820
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
821
            }
822
        }
823
    }
824
825
    /**
826
     * Gets the data service instance.
827
     *
828
     * @return IService
829
     */
830
    public function setService(IService $service)
831
    {
832
        $this->service = $service;
833
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
834
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
835
    }
836
}
837