Test Failed
Pull Request — master (#81)
by Alex
04:25
created

IronicSerialiser::writeUrlElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 16
rs 9.4285
cc 2
eloc 10
nc 2
nop 1
1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Serialisers;
4
5
use POData\Common\InvalidOperationException;
6
use POData\Common\Messages;
7
use POData\Common\ODataConstants;
8
use POData\Common\ODataException;
9
use POData\IService;
10
use POData\ObjectModel\IObjectSerialiser;
11
use POData\ObjectModel\ODataBagContent;
12
use POData\ObjectModel\ODataEntry;
13
use POData\ObjectModel\ODataFeed;
14
use POData\ObjectModel\ODataLink;
15
use POData\ObjectModel\ODataMediaLink;
16
use POData\ObjectModel\ODataNavigationPropertyInfo;
17
use POData\ObjectModel\ODataProperty;
18
use POData\ObjectModel\ODataPropertyContent;
19
use POData\ObjectModel\ODataURL;
20
use POData\ObjectModel\ODataURLCollection;
21
use POData\Providers\Metadata\ResourceEntityType;
22
use POData\Providers\Metadata\ResourceProperty;
23
use POData\Providers\Metadata\ResourcePropertyKind;
24
use POData\Providers\Metadata\ResourceSet;
25
use POData\Providers\Metadata\ResourceSetWrapper;
26
use POData\Providers\Metadata\ResourceType;
27
use POData\Providers\Metadata\ResourceTypeKind;
28
use POData\Providers\Metadata\Type\Binary;
29
use POData\Providers\Metadata\Type\Boolean;
30
use POData\Providers\Metadata\Type\DateTime;
31
use POData\Providers\Metadata\Type\IType;
32
use POData\Providers\Metadata\Type\StringType;
33
use POData\Providers\Query\QueryResult;
34
use POData\Providers\Query\QueryType;
35
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
36
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode;
37
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
38
use POData\UriProcessor\RequestDescription;
39
use POData\UriProcessor\SegmentStack;
40
41
class IronicSerialiser implements IObjectSerialiser
42
{
43
    /**
44
     * The service implementation.
45
     *
46
     * @var IService
47
     */
48
    protected $service;
49
50
    /**
51
     * Request description instance describes OData request the
52
     * the client has submitted and result of the request.
53
     *
54
     * @var RequestDescription
55
     */
56
    protected $request;
57
58
    /**
59
     * Collection of complex type instances used for cycle detection.
60
     *
61
     * @var array
62
     */
63
    protected $complexTypeInstanceCollection;
64
65
    /**
66
     * Absolute service Uri.
67
     *
68
     * @var string
69
     */
70
    protected $absoluteServiceUri;
71
72
    /**
73
     * Absolute service Uri with slash.
74
     *
75
     * @var string
76
     */
77
    protected $absoluteServiceUriWithSlash;
78
79
    /**
80
     * Holds reference to segment stack being processed.
81
     *
82
     * @var SegmentStack
83
     */
84
    protected $stack;
85
86
    /**
87
     * Lightweight stack tracking for recursive descent fill
88
     */
89
    private $lightStack = [];
90
91
    private $modelSerialiser;
92
93
    /**
94
     * @param IService           $service Reference to the data service instance
95
     * @param RequestDescription $request Type instance describing the client submitted request
96
     */
97
    public function __construct(IService $service, RequestDescription $request = null)
98
    {
99
        $this->service = $service;
100
        $this->request = $request;
101
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
102
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
103
        $this->stack = new SegmentStack($request);
104
        $this->complexTypeInstanceCollection = [];
105
        $this->modelSerialiser = new ModelSerialiser();
106
    }
107
108
    /**
109
     * Write a top level entry resource.
110
     *
111
     * @param mixed $entryObject Reference to the entry object to be written
112
     *
113
     * @return ODataEntry
114
     */
115
    public function writeTopLevelElement(QueryResult $entryObject)
116
    {
117
        if (!isset($entryObject->results)) {
118
            array_pop($this->lightStack);
119
            return null;
120
        }
121
122
        $this->loadStackIfEmpty();
123
124
        $stackCount = count($this->lightStack);
125
        $topOfStack = $this->lightStack[$stackCount-1];
126
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack[0]);
127
        $rawProp = $resourceType->getAllProperties();
128
        $relProp = [];
129
        $nonRelProp = [];
130
        foreach ($rawProp as $prop) {
131
            if ($prop->getResourceType() instanceof ResourceEntityType) {
132
                $relProp[] = $prop;
133
            } else {
134
                $nonRelProp[$prop->getName()] = $prop;
135
            }
136
        }
137
138
        $resourceSet = $resourceType->getCustomState();
139
        assert($resourceSet instanceof ResourceSet);
140
        $title = $resourceType->getName();
141
        $type = $resourceType->getFullName();
142
143
        $relativeUri = $this->getEntryInstanceKey(
144
            $entryObject->results,
145
            $resourceType,
146
            $resourceSet->getName()
147
        );
148
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
149
150
        list($mediaLink, $mediaLinks) = $this->writeMediaData($entryObject->results, $type, $relativeUri, $resourceType);
151
152
        $propertyContent = $this->writePrimitiveProperties($entryObject->results, $nonRelProp);
153
154
        $links = [];
155
        foreach ($relProp as $prop) {
156
            $nuLink = new ODataLink();
157
            $propKind = $prop->getKind();
158
159
            assert(
160
                ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
161
                || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind,
162
                '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
163
                .' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE'
164
            );
165
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
166
            $propType = 'application/atom+xml;type='.$propTail;
167
            $propName = $prop->getName();
168
            $nuLink->title = $propName;
169
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
170
            $nuLink->url = $relativeUri . '/' . $propName;
171
            $nuLink->type = $propType;
172
173
            $navProp = new ODataNavigationPropertyInfo($prop, $this->shouldExpandSegment($propName));
174
            if ($navProp->expanded) {
175
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
176
            }
177
178
            $links[] = $nuLink;
179
        }
180
181
        $odata = new ODataEntry();
182
        $odata->resourceSetName = $resourceSet->getName();
183
        $odata->id = $absoluteUri;
184
        $odata->title = $title;
185
        $odata->type = $type;
186
        $odata->propertyContent = $propertyContent;
187
        $odata->isMediaLinkEntry = $resourceType->isMediaLinkEntry();
188
        $odata->editLink = $relativeUri;
189
        $odata->mediaLink = $mediaLink;
190
        $odata->mediaLinks = $mediaLinks;
191
        $odata->links = $links;
192
193
        $newCount = count($this->lightStack);
194
        assert($newCount == $stackCount, "Should have $stackCount elements in stack, have $newCount elements");
195
        array_pop($this->lightStack);
196
        return $odata;
197
    }
198
199
    /**
200
     * Write top level feed element.
201
     *
202
     * @param array &$entryObjects Array of entry resources to be written
203
     *
204
     * @return ODataFeed
205
     */
206
    public function writeTopLevelElements(QueryResult &$entryObjects)
207
    {
208
        assert(is_array($entryObjects->results), '!is_array($entryObjects->results)');
209
210
        $this->loadStackIfEmpty();
211
        $setName = $this->getRequest()->getTargetResourceSetWrapper()->getName();
212
213
        $title = $this->getRequest()->getContainerName();
214
        $relativeUri = $this->getRequest()->getIdentifier();
215
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
216
217
        $selfLink = new ODataLink();
218
        $selfLink->name = 'self';
219
        $selfLink->title = $relativeUri;
220
        $selfLink->url = $relativeUri;
221
222
        $odata = new ODataFeed();
223
        $odata->title = $title;
224
        $odata->id = $absoluteUri;
225
        $odata->selfLink = $selfLink;
226
227
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
228
            $odata->rowCount = $this->getRequest()->getCountValue();
229
        }
230
        foreach ($entryObjects->results as $entry) {
231
            if (!$entry instanceof QueryResult) {
232
                $query = new QueryResult();
233
                $query->results = $entry;
234
            } else {
235
                $query = $entry;
236
            }
237
            $odata->entries[] = $this->writeTopLevelElement($query);
238
        }
239
240
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
241
        $requestTop = $this->getRequest()->getTopOptionCount();
242
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
243
        $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...
244
245
        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...
246
            $stackSegment = $setName;
247
            $lastObject = end($entryObjects->results);
248
            $segment = $this->getNextLinkUri($lastObject, $absoluteUri);
249
            $nextLink = new ODataLink();
250
            $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
251
            $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
252
            $odata->nextPageLink = $nextLink;
253
        }
254
255
        return $odata;
256
    }
257
258
    /**
259
     * Write top level url element.
260
     *
261
     * @param mixed $entryObject The entry resource whose url to be written
262
     *
263
     * @return ODataURL
264
     */
265
    public function writeUrlElement(QueryResult $entryObject)
266
    {
267
        $url = new ODataURL();
268
        if (!is_null($entryObject->results)) {
269
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
270
            $relativeUri = $this->getEntryInstanceKey(
271
                $entryObject->results,
272
                $currentResourceType,
273
                $this->getCurrentResourceSetWrapper()->getName()
274
            );
275
276
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
277
        }
278
279
        return $url;
280
    }
281
282
    /**
283
     * Write top level url collection.
284
     *
285
     * @param array $entryObjects Array of entry resources
286
     *                            whose url to be written
287
     *
288
     * @return ODataURLCollection
289
     */
290
    public function writeUrlElements(QueryResult $entryObjects)
291
    {
292
        $urls = new ODataURLCollection();
293
        if (!empty($entryObjects->results)) {
294
            $i = 0;
295
            foreach ($entryObjects->results as $entryObject) {
296
                $urls->urls[$i] = $this->writeUrlElement($entryObject);
297
                ++$i;
298
            }
299
300
            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...
301
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
302
                $lastObject = end($entryObjects->results);
303
                $segment = $this->getNextLinkUri($lastObject, $this->getRequest()->getRequestUrl()->getUrlAsString());
304
                $nextLink = new ODataLink();
305
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
306
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
307
                $urls->nextPageLink = $nextLink;
308
            }
309
        }
310
311
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
312
            $urls->count = $this->getRequest()->getCountValue();
313
        }
314
315
        return $urls;
316
    }
317
318
    /**
319
     * Write top level complex resource.
320
     *
321
     * @param mixed &$complexValue The complex object to be
322
     *                                    written
323
     * @param string $propertyName The name of the
324
     *                                    complex property
325
     * @param ResourceType &$resourceType Describes the type of
326
     *                                    complex object
327
     *
328
     * @return ODataPropertyContent
329
     */
330
    public function writeTopLevelComplexObject(QueryResult &$complexValue, $propertyName, ResourceType &$resourceType)
331
    {
332
        $result = $complexValue->results;
333
334
        $propertyContent = new ODataPropertyContent();
335
        $odataProperty = new ODataProperty();
336
        $odataProperty->name = $propertyName;
337
        $odataProperty->typeName = $resourceType->getFullName();
338
        if (null != $result) {
339
            $internalContent = $this->writeComplexValue($resourceType, $result);
0 ignored issues
show
Documentation introduced by
$result is of type array<integer,object>, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
340
            $odataProperty->value = $internalContent;
341
        }
342
343
        $propertyContent->properties[] = $odataProperty;
344
345
        return $propertyContent;
346
    }
347
348
    /**
349
     * Write top level bag resource.
350
     *
351
     * @param mixed &$BagValue The bag object to be
352
     *                                    written
353
     * @param string $propertyName The name of the
354
     *                                    bag property
355
     * @param ResourceType &$resourceType Describes the type of
356
     *                                    bag object
357
     * @return ODataPropertyContent
358
     */
359
    public function writeTopLevelBagObject(QueryResult &$BagValue, $propertyName, ResourceType &$resourceType)
360
    {
361
        $result = $BagValue->results;
362
363
        $propertyContent = new ODataPropertyContent();
364
        $odataProperty = new ODataProperty();
365
        $odataProperty->name = $propertyName;
366
        $odataProperty->typeName = 'Collection('.$resourceType->getFullName().')';
367
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
368
369
        $propertyContent->properties[] = $odataProperty;
370
        return $propertyContent;
371
    }
372
373
    /**
374
     * Write top level primitive value.
375
     *
376
     * @param mixed &$primitiveValue              The primitive value to be
377
     *                                            written
378
     * @param ResourceProperty &$resourceProperty Resource property describing the
379
     *                                            primitive property to be written
380
     * @return ODataPropertyContent
381
     */
382
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
383
    {
384
        assert(null != $resourceProperty, "Resource property must not be null");
385
        $propertyContent = new ODataPropertyContent();
386
387
        $odataProperty = new ODataProperty();
388
        $odataProperty->name = $resourceProperty->getName();
389
        $iType = $resourceProperty->getInstanceType();
390
        assert($iType instanceof IType, get_class($iType));
391
        $odataProperty->typeName = $iType->getFullTypeName();
392
        if (null == $primitiveValue->results) {
393
            $odataProperty->value = null;
394
        } else {
395
            $rType = $resourceProperty->getResourceType()->getInstanceType();
396
            $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 395 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...
397
        }
398
399
        $propertyContent->properties[] = $odataProperty;
400
401
        return $propertyContent;
402
    }
403
404
    /**
405
     * Gets reference to the request submitted by client.
406
     *
407
     * @return RequestDescription
408
     */
409
    public function getRequest()
410
    {
411
        assert(null != $this->request, 'Request not yet set');
412
413
        return $this->request;
414
    }
415
416
    /**
417
     * Sets reference to the request submitted by client.
418
     *
419
     * @param RequestDescription $request
420
     */
421
    public function setRequest(RequestDescription $request)
422
    {
423
        $this->request = $request;
424
        $this->stack->setRequest($request);
425
    }
426
427
    /**
428
     * Gets the data service instance.
429
     *
430
     * @return IService
431
     */
432
    public function getService()
433
    {
434
        return $this->service;
435
    }
436
437
    /**
438
     * Gets the segment stack instance.
439
     *
440
     * @return SegmentStack
441
     */
442
    public function getStack()
443
    {
444
        return $this->stack;
445
    }
446
447
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
448
    {
449
        $typeName = $resourceType->getName();
450
        $keyProperties = $resourceType->getKeyProperties();
451
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
452
        $keyString = $containerName . '(';
453
        $comma = null;
454
        foreach ($keyProperties as $keyName => $resourceProperty) {
455
            $keyType = $resourceProperty->getInstanceType();
456
            assert($keyType instanceof IType, '$keyType not instanceof IType');
457
            $keyName = $resourceProperty->getName();
458
            $keyValue = $entityInstance->$keyName;
459
            if (!isset($keyValue)) {
460
                throw ODataException::createInternalServerError(
461
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
462
                );
463
            }
464
465
            $keyValue = $keyType->convertToOData($keyValue);
466
            $keyString .= $comma . $keyName . '=' . $keyValue;
467
            $comma = ',';
468
        }
469
470
        $keyString .= ')';
471
472
        return $keyString;
473
    }
474
475
    /**
476
     * @param $entryObject
477
     * @param $type
478
     * @param $relativeUri
479
     * @param $resourceType
480
     * @return array
481
     */
482
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
483
    {
484
        $context = $this->getService()->getOperationContext();
485
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
486
        assert(null != $streamProviderWrapper, "Retrieved stream provider must not be null");
487
488
        $mediaLink = null;
489
        if ($resourceType->isMediaLinkEntry()) {
490
            $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...
491
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag);
492
        }
493
        $mediaLinks = [];
494
        if ($resourceType->hasNamedStream()) {
495
            $namedStreams = $resourceType->getAllNamedStreams();
496
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
497
                $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...
498
                    $entryObject,
499
                    $resourceStreamInfo,
500
                    $context,
501
                    $relativeUri
502
                );
503
                $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...
504
                    $entryObject,
505
                    $resourceStreamInfo,
506
                    $context
507
                );
508
                $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...
509
                    $entryObject,
510
                    $resourceStreamInfo,
511
                    $context
512
                );
513
514
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
515
                $mediaLinks[] = $nuLink;
516
            }
517
        }
518
        return [$mediaLink, $mediaLinks];
519
    }
520
521
    /**
522
     * Gets collection of projection nodes under the current node.
523
     *
524
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes
525
     *                                                        describing projections for the current segment, If this method returns
526
     *                                                        null it means no projections are to be applied and the entire resource
527
     *                                                        for the current segment should be serialized, If it returns non-null
528
     *                                                        only the properties described by the returned projection segments should
529
     *                                                        be serialized
530
     */
531
    protected function getProjectionNodes()
532
    {
533
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
534
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
535
            return null;
536
        }
537
538
        return $expandedProjectionNode->getChildNodes();
539
    }
540
541
    /**
542
     * Find a 'ExpandedProjectionNode' instance in the projection tree
543
     * which describes the current segment.
544
     *
545
     * @return ExpandedProjectionNode|null
546
     */
547
    protected function getCurrentExpandedProjectionNode()
548
    {
549
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
550
        if (is_null($expandedProjectionNode)) {
551
            return null;
552
        } else {
553
            $segmentNames = $this->getStack()->getSegmentNames();
554
            $depth = count($segmentNames);
555
            // $depth == 1 means serialization of root entry
556
            //(the resource identified by resource path) is going on,
557
            //so control won't get into the below for loop.
558
            //we will directly return the root node,
559
            //which is 'ExpandedProjectionNode'
560
            // for resource identified by resource path.
561
            if (0 != $depth) {
562
                for ($i = 1; $i < $depth; ++$i) {
563
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]);
564
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
565
                    assert(
566
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
567
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
568
                    );
569
                }
570
            }
571
        }
572
573
        return $expandedProjectionNode;
574
    }
575
576
    /**
577
     * Check whether to expand a navigation property or not.
578
     *
579
     * @param string $navigationPropertyName Name of naviagtion property in question
580
     *
581
     * @return bool True if the given navigation should be
582
     *              explanded otherwise false
583
     */
584
    protected function shouldExpandSegment($navigationPropertyName)
585
    {
586
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
587
        if (is_null($expandedProjectionNode)) {
588
            return false;
589
        }
590
591
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
592
593
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
594
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
595
    }
596
597
    /**
598
     * Wheter next link is needed for the current resource set (feed)
599
     * being serialized.
600
     *
601
     * @param int $resultSetCount Number of entries in the current
602
     *                            resource set
603
     *
604
     * @return bool true if the feed must have a next page link
605
     */
606
    protected function needNextPageLink($resultSetCount)
607
    {
608
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
609
        $recursionLevel = count($this->getStack()->getSegmentNames());
610
        $pageSize = $currentResourceSet->getResourceSetPageSize();
611
612
        if (1 == $recursionLevel) {
613
            //presence of $top option affect next link for root container
614
            $topValueCount = $this->getRequest()->getTopOptionCount();
615
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
616
                return false;
617
            }
618
        }
619
        return $resultSetCount == $pageSize;
620
    }
621
622
    /**
623
     * Resource set wrapper for the resource being serialized.
624
     *
625
     * @return ResourceSetWrapper
626
     */
627
    protected function getCurrentResourceSetWrapper()
628
    {
629
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
630
        $count = count($segmentWrappers);
631
632
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
633
    }
634
635
    /**
636
     * Get next page link from the given entity instance.
637
     *
638
     * @param mixed  &$lastObject Last object serialized to be
639
     *                            used for generating $skiptoken
640
     * @param string $absoluteUri Absolute response URI
641
     *
642
     * @return string for the link for next page
643
     */
644
    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...
645
    {
646
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
647
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
648
        assert(null != $internalOrderByInfo);
649
        assert(is_object($internalOrderByInfo));
650
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
651
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
652
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
653
654
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
655
        assert(!is_null($skipToken), '!is_null($skipToken)');
656
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
657
        $skipToken = '?'.$queryParameterString.$token.$skipToken;
658
659
        return $skipToken;
660
    }
661
662
    /**
663
     * Builds the string corresponding to query parameters for top level results
664
     * (result set identified by the resource path) to be put in next page link.
665
     *
666
     * @return string|null string representing the query parameters in the URI
667
     *                     query parameter format, NULL if there
668
     *                     is no query parameters
669
     *                     required for the next link of top level result set
670
     */
671
    protected function getNextPageLinkQueryParametersForRootResourceSet()
672
    {
673
        $queryParameterString = null;
674
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
675
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
676
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
677
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
678
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
679
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
680
            if (!is_null($value)) {
681
                if (!is_null($queryParameterString)) {
682
                    $queryParameterString = $queryParameterString . '&';
683
                }
684
685
                $queryParameterString .= $queryOption . '=' . $value;
686
            }
687
        }
688
689
        $topCountValue = $this->getRequest()->getTopOptionCount();
690
        if (!is_null($topCountValue)) {
691
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
692
            if (0 < $remainingCount) {
693
                if (!is_null($queryParameterString)) {
694
                    $queryParameterString .= '&';
695
                }
696
697
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
698
            }
699
        }
700
701
        if (!is_null($queryParameterString)) {
702
            $queryParameterString .= '&';
703
        }
704
705
        return $queryParameterString;
706
    }
707
708
    private function loadStackIfEmpty()
709
    {
710
        if (0 == count($this->lightStack)) {
711
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
712
            array_push($this->lightStack, [$typeName, $typeName]);
713
        }
714
    }
715
716
    /**
717
     * Convert the given primitive value to string.
718
     * Note: This method will not handle null primitive value.
719
     *
720
     * @param IType &$primitiveResourceType        Type of the primitive property
721
     *                                             whose value need to be converted
722
     * @param mixed        $primitiveValue         Primitive value to convert
723
     *
724
     * @return string
725
     */
726
    private function primitiveToString(IType &$type, $primitiveValue)
727
    {
728
        if ($type instanceof Boolean) {
729
            $stringValue = (true === $primitiveValue) ? 'true' : 'false';
730
        } elseif ($type instanceof Binary) {
731
            $stringValue = base64_encode($primitiveValue);
732
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
733
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
734
        } elseif ($type instanceof StringType) {
735
            $stringValue = utf8_encode($primitiveValue);
736
        } else {
737
            $stringValue = strval($primitiveValue);
738
        }
739
740
        return $stringValue;
741
    }
742
743
    /**
744
     * @param $entryObject
745
     * @param $nonRelProp
746
     * @return ODataPropertyContent
747
     */
748
    private function writePrimitiveProperties($entryObject, $nonRelProp)
749
    {
750
        $propertyContent = new ODataPropertyContent();
751
        $cereal = $this->modelSerialiser->bulkSerialise($entryObject);
752
        foreach ($cereal as $corn => $flake) {
753
            if (!array_key_exists($corn, $nonRelProp)) {
754
                continue;
755
            }
756
            $corn = strval($corn);
757
            $rType = $nonRelProp[$corn]->getResourceType()->getInstanceType();
758
            $subProp = new ODataProperty();
759
            $subProp->name = $corn;
760
            $subProp->value = isset($flake) ? $this->primitiveToString($rType, $flake) : null;
761
            $subProp->typeName = $nonRelProp[$corn]->getResourceType()->getFullName();
762
            $propertyContent->properties[] = $subProp;
763
        }
764
        return $propertyContent;
765
    }
766
767
    /**
768
     * @param $entryObject
769
     * @param $prop
770
     * @param $nuLink
771
     * @param $propKind
772
     * @param $propName
773
     */
774
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
775
    {
776
        $nextName = $prop->getResourceType()->getName();
777
        $nuLink->isExpanded = true;
778
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
779
        $nuLink->isCollection = $isCollection;
780
        $value = $entryObject->results->$propName;
781
        $result = new QueryResult();
782
        $result->results = $value;
783
        array_push($this->lightStack, [$nextName, $propName]);
784
        if (!$isCollection) {
785
            $expandedResult = $this->writeTopLevelElement($result);
786
        } else {
787
            $expandedResult = $this->writeTopLevelElements($result);
788
        }
789
        $nuLink->expandedResult = $expandedResult;
790
        if (!isset($nuLink->expandedResult)) {
791
            $nuLink->isCollection = null;
792
            $nuLink->isExpanded = null;
793
        } else {
794
            if (isset($nuLink->expandedResult->selfLink)) {
795
                $nuLink->expandedResult->selfLink->title = $propName;
796
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
797
                $nuLink->expandedResult->title = $propName;
798
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
799
            }
800
        }
801
    }
802
803
    /**
804
     * Gets the data service instance.
805
     *
806
     * @return IService
807
     */
808
    public function setService(IService $service)
809
    {
810
        $this->service = $service;
811
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
812
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
813
    }
814
815
    /**
816
     * @param ResourceType $resourceType
817
     * @param $result
818
     * @return ODataBagContent
819
     */
820
    protected function writeBagValue(ResourceType &$resourceType, $result)
821
    {
822
        assert(null == $result || is_array($result), 'Bag parameter must be null or array');
823
        $typeKind = $resourceType->getResourceTypeKind();
824
        assert(
825
            ResourceTypeKind::PRIMITIVE == $typeKind || ResourceTypeKind::COMPLEX == $typeKind,
826
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
827
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
828
        );
829
        if (null == $result) {
830
            return null;
831
        }
832
        $bag = new ODataBagContent();
833
        foreach ($result as $value) {
834
            if (isset($value)) {
835
                if (ResourceTypeKind::PRIMITIVE == $typeKind) {
836
                    $instance = $resourceType->getInstanceType();
837
                    assert($instance instanceof IType, get_class($instance));
838
                    $bag->propertyContents[] = $this->primitiveToString($instance, $value);
839
                } elseif (ResourceTypeKind::COMPLEX == $typeKind) {
840
                    $bag->propertyContents[] = $this->writeComplexValue($resourceType, $value);
841
                }
842
            }
843
        }
844
        return $bag;
845
    }
846
847
    /**
848
     * @param ResourceType $resourceType
849
     * @param object $result
850
     * @param string $propertyName
851
     * @return ODataPropertyContent
852
     */
853
    protected function writeComplexValue(ResourceType &$resourceType, &$result, $propertyName = null)
854
    {
855
        assert(is_object($result), 'Supplied $customObject must be an object');
856
857
        $count = count($this->complexTypeInstanceCollection);
858
        for ($i = 0; $i < $count; ++$i) {
859
            if ($this->complexTypeInstanceCollection[$i] === $result) {
860
                throw new InvalidOperationException(
861
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
862
                );
863
            }
864
        }
865
866
        $this->complexTypeInstanceCollection[$count] = &$result;
867
868
        $internalContent = new ODataPropertyContent();
869
        $resourceProperties = $resourceType->getAllProperties();
870
        // first up, handle primitive properties
871
        foreach ($resourceProperties as $prop) {
872
            $resourceKind = $prop->getKind();
873
            $propName = $prop->getName();
874
            $internalProperty = new ODataProperty();
875
            $internalProperty->name = $propName;
876
            if (static::isMatchPrimitive($resourceKind)) {
877
                $iType = $prop->getInstanceType();
878
                assert($iType instanceof IType, get_class($iType));
879
                $internalProperty->typeName = $iType->getFullTypeName();
880
881
                $rType = $prop->getResourceType()->getInstanceType();
882
                $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 881 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...
883
884
                $internalContent->properties[] = $internalProperty;
885
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
886
                $rType = $prop->getResourceType();
887
                $internalProperty->typeName = $rType->getFullName();
888
                $internalProperty->value = $this->writeComplexValue($rType, $result->$propName, $propName);
889
890
                $internalContent->properties[] = $internalProperty;
891
            }
892
        }
893
894
        unset($this->complexTypeInstanceCollection[$count]);
895
        return $internalContent;
896
    }
897
898
    public static function isMatchPrimitive($resourceKind)
899
    {
900
        if (16 > $resourceKind) {
901
            return false;
902
        }
903
        if (28 < $resourceKind) {
904
            return false;
905
        }
906
        return 0 == ($resourceKind % 4);
907
    }
908
}
909