Passed
Pull Request — master (#154)
by Alex
07:05
created

IronicSerialiser   F

Complexity

Total Complexity 127

Size/Duplication

Total Lines 1032
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 127
dl 0
loc 1032
rs 1.061
c 2
b 0
f 0

34 Methods

Rating   Name   Duplication   Size   Complexity  
A needNextPageLink() 0 14 4
A writeTopLevelBagObject() 0 12 1
A writePrimitiveProperties() 0 17 4
A __construct() 0 10 1
A getUpdated() 0 3 1
A getProjectionNodes() 0 8 3
B getCurrentExpandedProjectionNode() 0 30 5
B getEntryInstanceKey() 0 26 4
A isMatchPrimitive() 0 9 3
C expandNavigationProperty() 0 34 8
A getMetadata() 0 6 2
C writeBagValue() 0 25 8
A setService() 0 5 1
A writeTopLevelComplexObject() 0 16 2
A getService() 0 3 1
B getConcreteTypeFromAbstractType() 0 19 5
A getLightStack() 0 3 1
A getCurrentResourceSetWrapper() 0 6 2
C getNextPageLinkQueryParametersForRootResourceSet() 0 35 8
C writeUrlElements() 0 32 7
A getNextLinkUri() 0 17 3
C writeTopLevelElement() 0 123 11
A writeUrlElement() 0 15 2
C primitiveToString() 0 22 7
B writeComplexValue() 0 44 6
B writeMediaData() 0 37 4
A getRequest() 0 5 1
A setRequest() 0 4 1
A getModelSerialiser() 0 3 1
A getStack() 0 3 1
A writeTopLevelPrimitive() 0 21 2
A loadStackIfEmpty() 0 5 2
A shouldExpandSegment() 0 10 2
C writeTopLevelElements() 0 62 13

How to fix   Complexity   

Complex Class

Complex classes like IronicSerialiser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use IronicSerialiser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Serialisers;
4
5
use Carbon\Carbon;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Facades\App;
9
use POData\Common\InvalidOperationException;
10
use POData\Common\Messages;
11
use POData\Common\ODataConstants;
12
use POData\Common\ODataException;
13
use POData\IService;
14
use POData\ObjectModel\IObjectSerialiser;
15
use POData\ObjectModel\ODataBagContent;
16
use POData\ObjectModel\ODataCategory;
17
use POData\ObjectModel\ODataEntry;
18
use POData\ObjectModel\ODataFeed;
19
use POData\ObjectModel\ODataLink;
20
use POData\ObjectModel\ODataMediaLink;
21
use POData\ObjectModel\ODataNavigationPropertyInfo;
22
use POData\ObjectModel\ODataProperty;
23
use POData\ObjectModel\ODataPropertyContent;
24
use POData\ObjectModel\ODataTitle;
25
use POData\ObjectModel\ODataURL;
26
use POData\ObjectModel\ODataURLCollection;
27
use POData\Providers\Metadata\IMetadataProvider;
28
use POData\Providers\Metadata\ResourceEntityType;
29
use POData\Providers\Metadata\ResourceProperty;
30
use POData\Providers\Metadata\ResourcePropertyKind;
31
use POData\Providers\Metadata\ResourceSet;
32
use POData\Providers\Metadata\ResourceSetWrapper;
33
use POData\Providers\Metadata\ResourceType;
34
use POData\Providers\Metadata\ResourceTypeKind;
35
use POData\Providers\Metadata\Type\Binary;
36
use POData\Providers\Metadata\Type\Boolean;
37
use POData\Providers\Metadata\Type\DateTime;
38
use POData\Providers\Metadata\Type\IType;
39
use POData\Providers\Metadata\Type\StringType;
40
use POData\Providers\Query\QueryResult;
41
use POData\Providers\Query\QueryType;
42
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
43
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode;
44
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\RootProjectionNode;
45
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
46
use POData\UriProcessor\RequestDescription;
47
use POData\UriProcessor\SegmentStack;
48
49
class IronicSerialiser implements IObjectSerialiser
50
{
51
    const PK = 'PrimaryKey';
52
53
    private $propertiesCache = [];
54
55
    /**
56
     * @var RootProjectionNode
57
     */
58
    private $rootNode = null;
59
60
    /**
61
     * The service implementation.
62
     *
63
     * @var IService
64
     */
65
    protected $service;
66
67
    /**
68
     * Request description instance describes OData request the
69
     * the client has submitted and result of the request.
70
     *
71
     * @var RequestDescription
72
     */
73
    protected $request;
74
75
    /**
76
     * Collection of complex type instances used for cycle detection.
77
     *
78
     * @var array
79
     */
80
    protected $complexTypeInstanceCollection;
81
82
    /**
83
     * Absolute service Uri.
84
     *
85
     * @var string
86
     */
87
    protected $absoluteServiceUri;
88
89
    /**
90
     * Absolute service Uri with slash.
91
     *
92
     * @var string
93
     */
94
    protected $absoluteServiceUriWithSlash;
95
96
    /**
97
     * Holds reference to segment stack being processed.
98
     *
99
     * @var SegmentStack
100
     */
101
    protected $stack;
102
103
    /**
104
     * Lightweight stack tracking for recursive descent fill.
105
     */
106
    protected $lightStack = [];
107
108
    /**
109
     * @var ModelSerialiser
110
     */
111
    private $modelSerialiser;
112
113
114
115
    /**
116
     * @var IMetadataProvider
117
     */
118
    private $metaProvider;
119
120
    /*
121
     * Update time to insert into ODataEntry/ODataFeed fields
122
     * @var Carbon;
123
     */
124
    private $updated;
125
126
    /*
127
     * Has base URI already been written out during serialisation?
128
     * @var bool;
129
     */
130
    private $isBaseWritten = false;
131
132
    /**
133
     * @param IService                $service Reference to the data service instance
134
     * @param RequestDescription|null $request Type instance describing the client submitted request
135
     */
136
    public function __construct(IService $service, RequestDescription $request = null)
137
    {
138
        $this->service = $service;
139
        $this->request = $request;
140
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
141
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
142
        $this->stack = new SegmentStack($request);
143
        $this->complexTypeInstanceCollection = [];
144
        $this->modelSerialiser = new ModelSerialiser();
145
        $this->updated = Carbon::now();
146
    }
147
148
    /**
149
     * Write a top level entry resource.
150
     *
151
     * @param QueryResult $entryObject Reference to the entry object to be written
152
     *
153
     * @return ODataEntry|null
154
     */
155
    public function writeTopLevelElement(QueryResult $entryObject)
156
    {
157
        if (!isset($entryObject->results)) {
158
            array_pop($this->lightStack);
159
            return null;
160
        }
161
        assert($entryObject instanceof QueryResult, get_class($entryObject));
162
        assert($entryObject->results instanceof Model, get_class($entryObject->results));
0 ignored issues
show
Bug introduced by
It seems like $entryObject->results can also be of type array<mixed,object>; however, parameter $object of get_class() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
        assert($entryObject->results instanceof Model, get_class(/** @scrutinizer ignore-type */ $entryObject->results));
Loading history...
163
        $this->loadStackIfEmpty();
164
165
        $baseURI = $this->isBaseWritten ? null : $this->absoluteServiceUriWithSlash;
166
        $this->isBaseWritten = true;
167
168
        $stackCount = count($this->lightStack);
169
        $topOfStack = $this->lightStack[$stackCount-1];
170
        $payloadClass = get_class($entryObject->results);
171
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack['type']);
172
173
        // need gubbinz to unpack an abstract resource type
174
        $resourceType = $this->getConcreteTypeFromAbstractType($resourceType, $payloadClass);
175
176
        // make sure we're barking up right tree
177
        assert($resourceType instanceof ResourceEntityType, get_class($resourceType));
178
        $targClass = $resourceType->getInstanceType()->getName();
179
        assert(
180
            $entryObject->results instanceof $targClass,
181
            'Object being serialised not instance of expected class, ' . $targClass . ', is actually ' . $payloadClass
182
        );
183
184
        if (!array_key_exists($targClass, $this->propertiesCache)) {
185
            $rawProp = $resourceType->getAllProperties();
186
            $relProp = [];
187
            $nonRelProp = [];
188
            foreach ($rawProp as $prop) {
189
                $propType = $prop->getResourceType();
190
                if ($propType instanceof ResourceEntityType) {
191
                    $relProp[] = $prop;
192
                } else {
193
                    $nonRelProp[$prop->getName()] = ['prop' => $prop, 'type' => $propType->getInstanceType()];
194
                }
195
            }
196
            $this->propertiesCache[$targClass] = ['rel' => $relProp, 'nonRel' => $nonRelProp];
197
        }
198
        unset($relProp);
199
        unset($nonRelProp);
200
        $relProp = $this->propertiesCache[$targClass]['rel'];
201
        $nonRelProp = $this->propertiesCache[$targClass]['nonRel'];
202
203
        $resourceSet = $resourceType->getCustomState();
204
        assert($resourceSet instanceof ResourceSet);
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

204
        /** @scrutinizer ignore-call */ 
205
        assert($resourceSet instanceof ResourceSet);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
205
        $title = $resourceType->getName();
206
        $type = $resourceType->getFullName();
207
208
        $relativeUri = $this->getEntryInstanceKey(
209
            $entryObject->results,
210
            $resourceType,
211
            $resourceSet->getName()
212
        );
213
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
214
215
        list($mediaLink, $mediaLinks) = $this->writeMediaData(
216
            $entryObject->results,
217
            $type,
218
            $relativeUri,
219
            $resourceType
220
        );
221
222
        $propertyContent = $this->writePrimitiveProperties($entryObject->results, $nonRelProp);
0 ignored issues
show
Bug introduced by
It seems like $entryObject->results can also be of type array<mixed,object>; however, parameter $entryObject of AlgoWeb\PODataLaravel\Se...tePrimitiveProperties() does only seem to accept Illuminate\Database\Eloquent\Model, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

222
        $propertyContent = $this->writePrimitiveProperties(/** @scrutinizer ignore-type */ $entryObject->results, $nonRelProp);
Loading history...
223
224
        $links = [];
225
        foreach ($relProp as $prop) {
226
            $nuLink = new ODataLink();
227
            $propKind = $prop->getKind();
228
229
            assert(
230
                ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
231
                || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind,
232
                '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
233
                .' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE'
234
            );
235
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
236
            $propType = 'application/atom+xml;type=' . $propTail;
237
            $propName = $prop->getName();
238
            $nuLink->title = $propName;
239
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
240
            $nuLink->url = $relativeUri . '/' . $propName;
241
            $nuLink->type = $propType;
242
243
            $navProp = new ODataNavigationPropertyInfo($prop, $this->shouldExpandSegment($propName));
244
            if ($navProp->expanded) {
245
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
246
            }
247
248
            $links[] = $nuLink;
249
        }
250
251
        $odata = new ODataEntry();
252
        $odata->resourceSetName = $resourceSet->getName();
253
        $odata->id = $absoluteUri;
254
        $odata->title = new ODataTitle($title);
255
        $odata->type = new ODataCategory($type);
256
        $odata->propertyContent = $propertyContent;
257
        $odata->isMediaLinkEntry = $resourceType->isMediaLinkEntry();
258
        $odata->editLink = new ODataLink();
259
        $odata->editLink->url = $relativeUri;
260
        $odata->editLink->name = 'edit';
261
        $odata->editLink->title = $title;
262
        $odata->mediaLink = $mediaLink;
263
        $odata->mediaLinks = $mediaLinks;
264
        $odata->links = $links;
265
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
266
        $odata->baseURI = $baseURI;
267
268
        $newCount = count($this->lightStack);
269
        assert(
270
            $newCount == $stackCount,
271
            'Should have ' . $stackCount . ' elements in stack, have ' . $newCount . ' elements'
272
        );
273
        $this->lightStack[$newCount-1]['count']--;
274
        if (0 == $this->lightStack[$newCount-1]['count']) {
275
            array_pop($this->lightStack);
276
        }
277
        return $odata;
278
    }
279
280
    /**
281
     * Write top level feed element.
282
     *
283
     * @param QueryResult &$entryObjects Array of entry resources to be written
284
     *
285
     * @return ODataFeed
286
     */
287
    public function writeTopLevelElements(QueryResult & $entryObjects)
288
    {
289
        $res = $entryObjects->results;
290
        assert(is_array($res) || $res instanceof Collection, '!is_array($entryObjects->results)');
291
        if (is_array($res) && 0 == count($res)) {
292
            $entryObjects->hasMore = false;
293
        }
294
        if ($res instanceof Collection && 0 == $res->count()) {
295
            $entryObjects->hasMore = false;
296
        }
297
298
        $this->loadStackIfEmpty();
299
        $setName = $this->getRequest()->getTargetResourceSetWrapper()->getName();
300
301
        $title = $this->getRequest()->getContainerName();
302
        $relativeUri = $this->getRequest()->getIdentifier();
303
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
304
305
        $selfLink = new ODataLink();
306
        $selfLink->name = 'self';
307
        $selfLink->title = $relativeUri;
308
        $selfLink->url = $relativeUri;
309
310
        $odata = new ODataFeed();
311
        $odata->title = new ODataTitle($title);
312
        $odata->id = $absoluteUri;
313
        $odata->selfLink = $selfLink;
314
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
315
        $odata->baseURI = $this->isBaseWritten ? null : $this->absoluteServiceUriWithSlash;
316
        $this->isBaseWritten = true;
317
318
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
319
            $odata->rowCount = $this->getRequest()->getCountValue();
320
        }
321
        foreach ($res as $entry) {
322
            if (!$entry instanceof QueryResult) {
323
                $query = new QueryResult();
324
                $query->results = $entry;
325
            } else {
326
                $query = $entry;
327
            }
328
            assert($query instanceof QueryResult, get_class($query));
329
            assert($query->results instanceof Model, get_class($query->results));
330
            $odata->entries[] = $this->writeTopLevelElement($query);
331
        }
332
333
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
334
        $requestTop = $this->getRequest()->getTopOptionCount();
335
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
336
        $requestTop = (null === $requestTop) ? $pageSize+1 : $requestTop;
337
338
        if (true === $entryObjects->hasMore && $requestTop > $pageSize) {
339
            $stackSegment = $setName;
340
            $lastObject = end($entryObjects->results);
341
            $segment = $this->getNextLinkUri($lastObject);
342
            $nextLink = new ODataLink();
343
            $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
344
            $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
345
            $odata->nextPageLink = $nextLink;
346
        }
347
348
        return $odata;
349
    }
350
351
    /**
352
     * Write top level url element.
353
     *
354
     * @param QueryResult $entryObject The entry resource whose url to be written
355
     *
356
     * @return ODataURL
357
     */
358
    public function writeUrlElement(QueryResult $entryObject)
359
    {
360
        $url = new ODataURL();
361
        if (null !== $entryObject->results) {
362
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
363
            $relativeUri = $this->getEntryInstanceKey(
364
                $entryObject->results,
365
                $currentResourceType,
366
                $this->getCurrentResourceSetWrapper()->getName()
367
            );
368
369
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
370
        }
371
372
        return $url;
373
    }
374
375
    /**
376
     * Write top level url collection.
377
     *
378
     * @param QueryResult $entryObjects Array of entry resources whose url to be written
379
     *
380
     * @return ODataURLCollection
381
     */
382
    public function writeUrlElements(QueryResult $entryObjects)
383
    {
384
        $urls = new ODataURLCollection();
385
        if (!empty($entryObjects->results)) {
386
            $i = 0;
387
            foreach ($entryObjects->results as $entryObject) {
388
                if (!$entryObject instanceof QueryResult) {
389
                    $query = new QueryResult();
390
                    $query->results = $entryObject;
391
                } else {
392
                    $query = $entryObject;
393
                }
394
                $urls->urls[$i] = $this->writeUrlElement($query);
395
                ++$i;
396
            }
397
398
            if ($i > 0 && true === $entryObjects->hasMore) {
399
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
400
                $lastObject = end($entryObjects->results);
0 ignored issues
show
Bug introduced by
It seems like $entryObjects->results can also be of type object; however, parameter $array of end() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

400
                $lastObject = end(/** @scrutinizer ignore-type */ $entryObjects->results);
Loading history...
401
                $segment = $this->getNextLinkUri($lastObject);
402
                $nextLink = new ODataLink();
403
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
404
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
405
                $urls->nextPageLink = $nextLink;
406
            }
407
        }
408
409
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
410
            $urls->count = $this->getRequest()->getCountValue();
411
        }
412
413
        return $urls;
414
    }
415
416
    /**
417
     * Write top level complex resource.
418
     *
419
     * @param QueryResult  &$complexValue The complex object to be written
420
     * @param string       $propertyName  The name of the complex property
421
     * @param ResourceType &$resourceType Describes the type of complex object
422
     *
423
     * @return ODataPropertyContent
424
     */
425
    public function writeTopLevelComplexObject(QueryResult & $complexValue, $propertyName, ResourceType & $resourceType)
426
    {
427
        $result = $complexValue->results;
428
429
        $propertyContent = new ODataPropertyContent();
430
        $odataProperty = new ODataProperty();
431
        $odataProperty->name = $propertyName;
432
        $odataProperty->typeName = $resourceType->getFullName();
433
        if (null != $result) {
434
            $internalContent = $this->writeComplexValue($resourceType, $result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type array<mixed,object>; however, parameter $result of AlgoWeb\PODataLaravel\Se...er::writeComplexValue() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

434
            $internalContent = $this->writeComplexValue($resourceType, /** @scrutinizer ignore-type */ $result);
Loading history...
435
            $odataProperty->value = $internalContent;
436
        }
437
438
        $propertyContent->properties[$propertyName] = $odataProperty;
439
440
        return $propertyContent;
441
    }
442
443
    /**
444
     * Write top level bag resource.
445
     *
446
     * @param QueryResult  &$BagValue     The bag object to be
447
     *                                    written
448
     * @param string       $propertyName  The name of the
449
     *                                    bag property
450
     * @param ResourceType &$resourceType Describes the type of
451
     *                                    bag object
452
     *
453
     * @return ODataPropertyContent
454
     */
455
    public function writeTopLevelBagObject(QueryResult & $BagValue, $propertyName, ResourceType & $resourceType)
456
    {
457
        $result = $BagValue->results;
458
459
        $propertyContent = new ODataPropertyContent();
460
        $odataProperty = new ODataProperty();
461
        $odataProperty->name = $propertyName;
462
        $odataProperty->typeName = 'Collection(' . $resourceType->getFullName() . ')';
463
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
464
465
        $propertyContent->properties[$propertyName] = $odataProperty;
466
        return $propertyContent;
467
    }
468
469
    /**
470
     * Write top level primitive value.
471
     *
472
     * @param  QueryResult          &$primitiveValue   The primitive value to be
473
     *                                                 written
474
     * @param  ResourceProperty     &$resourceProperty Resource property describing the
475
     *                                                 primitive property to be written
476
     * @return ODataPropertyContent
477
     */
478
    public function writeTopLevelPrimitive(QueryResult & $primitiveValue, ResourceProperty & $resourceProperty = null)
479
    {
480
        assert(null != $resourceProperty, 'Resource property must not be null');
481
        $propertyContent = new ODataPropertyContent();
482
483
        $odataProperty = new ODataProperty();
484
        $odataProperty->name = $resourceProperty->getName();
0 ignored issues
show
Bug introduced by
The method getName() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

484
        /** @scrutinizer ignore-call */ 
485
        $odataProperty->name = $resourceProperty->getName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
485
        $iType = $resourceProperty->getInstanceType();
486
        assert($iType instanceof IType, get_class($iType));
487
        $odataProperty->typeName = $iType->getFullTypeName();
0 ignored issues
show
Bug introduced by
The method getFullTypeName() does not exist on ReflectionClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

487
        /** @scrutinizer ignore-call */ 
488
        $odataProperty->typeName = $iType->getFullTypeName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
488
        if (null == $primitiveValue->results) {
489
            $odataProperty->value = null;
490
        } else {
491
            $rType = $resourceProperty->getResourceType()->getInstanceType();
492
            assert($rType instanceof IType, get_class($rType));
493
            $odataProperty->value = $this->primitiveToString($rType, $primitiveValue->results);
0 ignored issues
show
Bug introduced by
It seems like $rType can also be of type ReflectionClass; however, parameter $type of AlgoWeb\PODataLaravel\Se...er::primitiveToString() does only seem to accept POData\Providers\Metadata\Type\IType, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

493
            $odataProperty->value = $this->primitiveToString(/** @scrutinizer ignore-type */ $rType, $primitiveValue->results);
Loading history...
494
        }
495
496
        $propertyContent->properties[$odataProperty->name] = $odataProperty;
497
498
        return $propertyContent;
499
    }
500
501
    /**
502
     * Gets reference to the request submitted by client.
503
     *
504
     * @return RequestDescription
505
     */
506
    public function getRequest()
507
    {
508
        assert(null != $this->request, 'Request not yet set');
509
510
        return $this->request;
511
    }
512
513
    /**
514
     * Sets reference to the request submitted by client.
515
     *
516
     * @param RequestDescription $request
517
     */
518
    public function setRequest(RequestDescription $request)
519
    {
520
        $this->request = $request;
521
        $this->stack->setRequest($request);
522
    }
523
524
    /**
525
     * Gets the data service instance.
526
     *
527
     * @return IService
528
     */
529
    public function getService()
530
    {
531
        return $this->service;
532
    }
533
534
    /**
535
     * Gets the segment stack instance.
536
     *
537
     * @return SegmentStack
538
     */
539
    public function getStack()
540
    {
541
        return $this->stack;
542
    }
543
544
    /**
545
     * Get update timestamp.
546
     *
547
     * @return Carbon
548
     */
549
    public function getUpdated()
550
    {
551
        return $this->updated;
552
    }
553
554
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
555
    {
556
        $typeName = $resourceType->getName();
557
        $keyProperties = $resourceType->getKeyProperties();
558
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
559
        $keyString = $containerName . '(';
560
        $comma = null;
561
        foreach ($keyProperties as $keyName => $resourceProperty) {
562
            $keyType = $resourceProperty->getInstanceType();
563
            assert($keyType instanceof IType, '$keyType not instanceof IType');
564
            $keyName = $resourceProperty->getName();
565
            $keyValue = (self::PK == $keyName) ? $entityInstance->getKey() : $entityInstance->$keyName;
566
            if (!isset($keyValue)) {
567
                throw ODataException::createInternalServerError(
568
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
569
                );
570
            }
571
572
            $keyValue = $keyType->convertToOData($keyValue);
0 ignored issues
show
Bug introduced by
The method convertToOData() does not exist on ReflectionClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

572
            /** @scrutinizer ignore-call */ 
573
            $keyValue = $keyType->convertToOData($keyValue);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
573
            $keyString .= $comma . $keyName . '=' . $keyValue;
574
            $comma = ',';
575
        }
576
577
        $keyString .= ')';
578
579
        return $keyString;
580
    }
581
582
    /**
583
     * @param $entryObject
584
     * @param $type
585
     * @param $relativeUri
586
     * @param $resourceType
587
     * @return array<ODataMediaLink|null|array>
588
     */
589
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
590
    {
591
        $context = $this->getService()->getOperationContext();
592
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
593
        assert(null != $streamProviderWrapper, 'Retrieved stream provider must not be null');
594
595
        $mediaLink = null;
596
        if ($resourceType->isMediaLinkEntry()) {
597
            $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()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

597
            /** @scrutinizer ignore-call */ 
598
            $eTag = $streamProviderWrapper->getStreamETag2($entryObject, null, $context);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
598
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag, 'edit-media');
599
        }
600
        $mediaLinks = [];
601
        if ($resourceType->hasNamedStream()) {
602
            $namedStreams = $resourceType->getAllNamedStreams();
603
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
604
                $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 getReadStreamUri()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

604
                /** @scrutinizer ignore-call */ 
605
                $readUri = $streamProviderWrapper->getReadStreamUri2(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
605
                    $entryObject,
606
                    $resourceStreamInfo,
607
                    $context,
608
                    $relativeUri
609
                );
610
                $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()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

610
                /** @scrutinizer ignore-call */ 
611
                $mediaContentType = $streamProviderWrapper->getStreamContentType2(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
611
                    $entryObject,
612
                    $resourceStreamInfo,
613
                    $context
614
                );
615
                $eTag = $streamProviderWrapper->getStreamETag2(
616
                    $entryObject,
617
                    $resourceStreamInfo,
618
                    $context
619
                );
620
621
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
622
                $mediaLinks[] = $nuLink;
623
            }
624
        }
625
        return [$mediaLink, $mediaLinks];
626
    }
627
628
    /**
629
     * Gets collection of projection nodes under the current node.
630
     *
631
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes describing projections for the current
632
     *                                                        segment, If this method returns null it means no
633
     *                                                        projections are to be applied and the entire resource for
634
     *                                                        the current segment should be serialized, If it returns
635
     *                                                        non-null only the properties described by the returned
636
     *                                                        projection segments should be serialized
637
     */
638
    protected function getProjectionNodes()
639
    {
640
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
641
        if (null === $expandedProjectionNode || $expandedProjectionNode->canSelectAllProperties()) {
642
            return null;
643
        }
644
645
        return $expandedProjectionNode->getChildNodes();
646
    }
647
648
    /**
649
     * Find a 'ExpandedProjectionNode' instance in the projection tree
650
     * which describes the current segment.
651
     *
652
     * @return null|RootProjectionNode|ExpandedProjectionNode
653
     */
654
    protected function getCurrentExpandedProjectionNode()
655
    {
656
        if (null === $this->rootNode) {
657
            $this->rootNode = $this->getRequest()->getRootProjectionNode();
658
        }
659
        $expandedProjectionNode = $this->rootNode;
660
        if (null === $expandedProjectionNode) {
661
            return null;
662
        } else {
663
            $segmentNames = $this->getLightStack();
664
            $depth = count($segmentNames);
665
            // $depth == 1 means serialization of root entry
666
            //(the resource identified by resource path) is going on,
667
            //so control won't get into the below for loop.
668
            //we will directly return the root node,
669
            //which is 'ExpandedProjectionNode'
670
            // for resource identified by resource path.
671
            if (0 != $depth) {
672
                for ($i = 1; $i < $depth; ++$i) {
673
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]['prop']);
0 ignored issues
show
introduced by
The method findNode() does not exist on POData\UriProcessor\Quer...onParser\ProjectionNode. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

673
                    /** @scrutinizer ignore-call */ 
674
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]['prop']);
Loading history...
674
                    assert(null !== $expandedProjectionNode, 'is_null($expandedProjectionNode)');
675
                    assert(
676
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
677
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
678
                    );
679
                }
680
            }
681
        }
682
683
        return $expandedProjectionNode;
684
    }
685
686
    /**
687
     * Check whether to expand a navigation property or not.
688
     *
689
     * @param string $navigationPropertyName Name of naviagtion property in question
690
     *
691
     * @return bool True if the given navigation should be expanded, otherwise false
692
     */
693
    protected function shouldExpandSegment($navigationPropertyName)
694
    {
695
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
696
        if (null === $expandedProjectionNode) {
697
            return false;
698
        }
699
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
700
701
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
702
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
703
    }
704
705
    /**
706
     * Wheter next link is needed for the current resource set (feed)
707
     * being serialized.
708
     *
709
     * @param int $resultSetCount Number of entries in the current
710
     *                            resource set
711
     *
712
     * @return bool true if the feed must have a next page link
713
     */
714
    protected function needNextPageLink($resultSetCount)
715
    {
716
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
717
        $recursionLevel = count($this->getStack()->getSegmentNames());
718
        $pageSize = $currentResourceSet->getResourceSetPageSize();
719
720
        if (1 == $recursionLevel) {
721
            //presence of $top option affect next link for root container
722
            $topValueCount = $this->getRequest()->getTopOptionCount();
723
            if (null !== $topValueCount && ($topValueCount <= $pageSize)) {
724
                return false;
725
            }
726
        }
727
        return $resultSetCount == $pageSize;
728
    }
729
730
    /**
731
     * Resource set wrapper for the resource being serialized.
732
     *
733
     * @return ResourceSetWrapper
734
     */
735
    protected function getCurrentResourceSetWrapper()
736
    {
737
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
738
        $count = count($segmentWrappers);
739
740
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count-1];
741
    }
742
743
    /**
744
     * Get next page link from the given entity instance.
745
     *
746
     * @param  mixed          &$lastObject Last object serialized to be
747
     *                                     used for generating
748
     *                                     $skiptoken
749
     * @throws ODataException
750
     * @return string         for the link for next page
751
     */
752
    protected function getNextLinkUri(&$lastObject)
753
    {
754
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
755
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
756
        assert(null != $internalOrderByInfo);
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

756
        /** @scrutinizer ignore-call */ 
757
        assert(null != $internalOrderByInfo);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
757
        assert(is_object($internalOrderByInfo));
758
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
759
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
760
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $queryParameterString is correct as $this->getNextPageLinkQu...ersForRootResourceSet() targeting AlgoWeb\PODataLaravel\Se...ersForRootResourceSet() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
761
762
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
763
        assert(null !== $skipToken, '!is_null($skipToken)');
764
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
765
        $skipToken = (1 < $numSegments) ? $skipToken : intval(trim($skipToken, '\''));
766
        $skipToken = '?' . $queryParameterString . $token . $skipToken;
767
768
        return $skipToken;
769
    }
770
771
    /**
772
     * Builds the string corresponding to query parameters for top level results
773
     * (result set identified by the resource path) to be put in next page link.
774
     *
775
     * @return string|null string representing the query parameters in the URI
776
     *                     query parameter format, NULL if there
777
     *                     is no query parameters
778
     *                     required for the next link of top level result set
779
     */
780
    protected function getNextPageLinkQueryParametersForRootResourceSet()
781
    {
782
        $queryParameterString = null;
783
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
784
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
785
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
786
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
787
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
788
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
789
            if (null !== $value) {
790
                if (null !== $queryParameterString) {
791
                    $queryParameterString = $queryParameterString . '&';
792
                }
793
794
                $queryParameterString .= $queryOption . '=' . $value;
795
            }
796
        }
797
798
        $topCountValue = $this->getRequest()->getTopOptionCount();
799
        if (null !== $topCountValue) {
800
            $remainingCount = $topCountValue-$this->getRequest()->getTopCount();
801
            if (0 < $remainingCount) {
802
                if (null !== $queryParameterString) {
803
                    $queryParameterString .= '&';
804
                }
805
806
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
807
            }
808
        }
809
810
        if (null !== $queryParameterString) {
811
            $queryParameterString .= '&';
812
        }
813
814
        return $queryParameterString;
815
    }
816
817
    private function loadStackIfEmpty()
818
    {
819
        if (0 == count($this->lightStack)) {
820
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
821
            array_push($this->lightStack, ['type' => $typeName, 'property' => $typeName, 'count' => 1]);
822
        }
823
    }
824
825
    /**
826
     * Convert the given primitive value to string.
827
     * Note: This method will not handle null primitive value.
828
     *
829
     * @param IType &$primitiveResourceType Type of the primitive property
830
     *                                      whose value need to be converted
831
     * @param mixed $primitiveValue         Primitive value to convert
832
     *
833
     * @return string
834
     */
835
    private function primitiveToString(IType & $type, $primitiveValue)
836
    {
837
        // kludge to enable switching on type of $type without getting tripped up by mocks as we would with get_class
838
        // switch (true) means we unconditionally enter, and then lean on case statements to match given block
839
        switch (true) {
840
            case $type instanceof StringType:
841
                $stringValue = utf8_encode($primitiveValue);
842
                break;
843
            case $type instanceof Boolean:
844
                $stringValue = (true === $primitiveValue) ? 'true' : 'false';
845
                break;
846
            case $type instanceof Binary:
847
                $stringValue = base64_encode($primitiveValue);
848
                break;
849
            case $type instanceof DateTime && $primitiveValue instanceof \DateTime:
850
                $stringValue = $primitiveValue->format(\DateTime::ATOM);
851
                break;
852
            default:
853
                $stringValue = strval($primitiveValue);
854
        }
855
856
        return $stringValue;
857
    }
858
859
    /**
860
     * @param $entryObject
861
     * @param $nonRelProp
862
     * @return ODataPropertyContent
863
     */
864
    private function writePrimitiveProperties(Model $entryObject, $nonRelProp)
865
    {
866
        $propertyContent = new ODataPropertyContent();
867
        $cereal = $this->getModelSerialiser()->bulkSerialise($entryObject);
868
        foreach ($cereal as $corn => $flake) {
869
            if (!array_key_exists($corn, $nonRelProp)) {
870
                continue;
871
            }
872
            $corn = strval($corn);
873
            $rType = $nonRelProp[$corn]['type'];
874
            $subProp = new ODataProperty();
875
            $subProp->name = $corn;
876
            $subProp->value = isset($flake) ? $this->primitiveToString($rType, $flake) : null;
877
            $subProp->typeName = $nonRelProp[$corn]['prop']->getResourceType()->getFullName();
878
            $propertyContent->properties[$corn] = $subProp;
879
        }
880
        return $propertyContent;
881
    }
882
883
    /**
884
     * @param $entryObject
885
     * @param $prop
886
     * @param $nuLink
887
     * @param $propKind
888
     * @param $propName
889
     */
890
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
891
    {
892
        $nextName = $prop->getResourceType()->getName();
893
        $nuLink->isExpanded = true;
894
        $value = $entryObject->results->$propName;
895
        $isCombo = is_array($value) || $value instanceof Collection;
896
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind || $isCombo;
897
        $nuLink->isCollection = $isCollection;
898
899
        $result = new QueryResult();
900
        $result->results = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value can also be of type Illuminate\Support\Collection. However, the property $results is declared as type object|array<mixed,object>|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
901
        $resultCount = $isCollection ? count($value) : 1;
902
903
        if (0 < $resultCount) {
904
            $newStackLine = ['type' => $nextName, 'prop' => $propName, 'count' => $resultCount];
905
            array_push($this->lightStack, $newStackLine);
906
            if (!$isCollection) {
907
                $nuLink->type = 'application/atom+xml;type=entry';
908
                $expandedResult = $this->writeTopLevelElement($result);
909
            } else {
910
                $nuLink->type = 'application/atom+xml;type=feed';
911
                $expandedResult = $this->writeTopLevelElements($result);
912
            }
913
            $nuLink->expandedResult = $expandedResult;
914
        }
915
        if (!isset($nuLink->expandedResult)) {
916
            $nuLink->isCollection = null;
917
            $nuLink->isExpanded = null;
918
        } else {
919
            if (isset($nuLink->expandedResult->selfLink)) {
920
                $nuLink->expandedResult->selfLink->title = $propName;
921
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
922
                $nuLink->expandedResult->title = new ODataTitle($propName);
923
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
924
            }
925
        }
926
    }
927
928
    /**
929
     * Gets the data service instance.
930
     *
931
     * @return void
932
     */
933
    public function setService(IService $service)
934
    {
935
        $this->service = $service;
936
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
937
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
938
    }
939
940
    /**
941
     * @param ResourceType $resourceType
942
     * @param $result
943
     * @return ODataBagContent|null
944
     */
945
    protected function writeBagValue(ResourceType & $resourceType, $result)
946
    {
947
        assert(null == $result || is_array($result), 'Bag parameter must be null or array');
948
        $typeKind = $resourceType->getResourceTypeKind();
949
        assert(
950
            ResourceTypeKind::PRIMITIVE() == $typeKind || ResourceTypeKind::COMPLEX() == $typeKind,
951
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
952
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
953
        );
954
        if (null == $result) {
955
            return null;
956
        }
957
        $bag = new ODataBagContent();
958
        foreach ($result as $value) {
959
            if (isset($value)) {
960
                if (ResourceTypeKind::PRIMITIVE() == $typeKind) {
961
                    $instance = $resourceType->getInstanceType();
962
                    assert($instance instanceof IType, get_class($instance));
963
                    $bag->propertyContents[] = $this->primitiveToString($instance, $value);
0 ignored issues
show
Bug introduced by
It seems like $instance can also be of type ReflectionClass; however, parameter $type of AlgoWeb\PODataLaravel\Se...er::primitiveToString() does only seem to accept POData\Providers\Metadata\Type\IType, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

963
                    $bag->propertyContents[] = $this->primitiveToString(/** @scrutinizer ignore-type */ $instance, $value);
Loading history...
964
                } elseif (ResourceTypeKind::COMPLEX() == $typeKind) {
965
                    $bag->propertyContents[] = $this->writeComplexValue($resourceType, $value);
966
                }
967
            }
968
        }
969
        return $bag;
970
    }
971
972
    /**
973
     * @param  ResourceType         $resourceType
974
     * @param  object               $result
975
     * @param  string|null          $propertyName
976
     * @return ODataPropertyContent
977
     */
978
    protected function writeComplexValue(ResourceType & $resourceType, &$result, $propertyName = null)
979
    {
980
        assert(is_object($result), 'Supplied $customObject must be an object');
981
982
        $count = count($this->complexTypeInstanceCollection);
983
        for ($i = 0; $i < $count; ++$i) {
984
            if ($this->complexTypeInstanceCollection[$i] === $result) {
985
                throw new InvalidOperationException(
986
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
987
                );
988
            }
989
        }
990
991
        $this->complexTypeInstanceCollection[$count] = &$result;
992
993
        $internalContent = new ODataPropertyContent();
994
        $resourceProperties = $resourceType->getAllProperties();
995
        // first up, handle primitive properties
996
        foreach ($resourceProperties as $prop) {
997
            $resourceKind = $prop->getKind();
998
            $propName = $prop->getName();
999
            $internalProperty = new ODataProperty();
1000
            $internalProperty->name = $propName;
1001
            if (static::isMatchPrimitive($resourceKind)) {
1002
                $iType = $prop->getInstanceType();
1003
                assert($iType instanceof IType, get_class($iType));
1004
                $internalProperty->typeName = $iType->getFullTypeName();
1005
1006
                $rType = $prop->getResourceType()->getInstanceType();
1007
                assert($rType instanceof IType, get_class($rType));
1008
                $internalProperty->value = $this->primitiveToString($rType, $result->$propName);
0 ignored issues
show
Bug introduced by
It seems like $rType can also be of type ReflectionClass; however, parameter $type of AlgoWeb\PODataLaravel\Se...er::primitiveToString() does only seem to accept POData\Providers\Metadata\Type\IType, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1008
                $internalProperty->value = $this->primitiveToString(/** @scrutinizer ignore-type */ $rType, $result->$propName);
Loading history...
1009
1010
                $internalContent->properties[$propName] = $internalProperty;
1011
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
1012
                $rType = $prop->getResourceType();
1013
                $internalProperty->typeName = $rType->getFullName();
1014
                $internalProperty->value = $this->writeComplexValue($rType, $result->$propName, $propName);
1015
1016
                $internalContent->properties[$propName] = $internalProperty;
1017
            }
1018
        }
1019
1020
        unset($this->complexTypeInstanceCollection[$count]);
1021
        return $internalContent;
1022
    }
1023
1024
    public static function isMatchPrimitive($resourceKind)
1025
    {
1026
        if (16 > $resourceKind) {
1027
            return false;
1028
        }
1029
        if (28 < $resourceKind) {
1030
            return false;
1031
        }
1032
        return 0 == ($resourceKind % 4);
1033
    }
1034
1035
    /*
1036
     * @return IMetadataProvider
1037
     */
1038
    protected function getMetadata()
1039
    {
1040
        if (null == $this->metaProvider) {
1041
            $this->metaProvider = App::make('metadata');
1042
        }
1043
        return $this->metaProvider;
1044
    }
1045
1046
    /**
1047
     * @return array
1048
     */
1049
    protected function getLightStack()
1050
    {
1051
        return $this->lightStack;
1052
    }
1053
1054
    /**
1055
     * @return ModelSerialiser
1056
     */
1057
    public function getModelSerialiser()
1058
    {
1059
        return $this->modelSerialiser;
1060
    }
1061
1062
    protected function getConcreteTypeFromAbstractType(ResourceEntityType $resourceType, $payloadClass)
1063
    {
1064
        if ($resourceType->isAbstract()) {
1065
            $derived = $this->getMetadata()->getDerivedTypes($resourceType);
1066
            assert(0 < count($derived), 'Supplied abstract type must have at least one derived type');
1067
            foreach ($derived as $rawType) {
1068
                if (!$rawType->isAbstract()) {
1069
                    $name = $rawType->getInstanceType()->getName();
1070
                    if ($payloadClass == $name) {
1071
                        $resourceType = $rawType;
1072
                        break;
1073
                    }
1074
                }
1075
            }
1076
        }
1077
        // despite all set up, checking, etc, if we haven't picked a concrete resource type,
1078
        // wheels have fallen off, so blow up
1079
        assert(!$resourceType->isAbstract(), 'Concrete resource type not selected for payload ' . $payloadClass);
1080
        return $resourceType;
1081
    }
1082
}
1083