Passed
Pull Request — master (#206)
by Alex
04:45
created

IronicSerialiser::isMatchPrimitive()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
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
    use SerialiseDepWrapperTrait;
52
    use SerialisePropertyCacheTrait;
53
    use SerialiseNavigationTrait;
54
    use SerialiseLowLevelWritersTrait;
55
    use SerialiseNextPageLinksTrait;
56
57
    /**
58
     * Update time to insert into ODataEntry/ODataFeed fields
59
     * @var Carbon
60
     */
61
    private $updated;
62
63
    /**
64
     * Has base URI already been written out during serialisation?
65
     * @var bool
66
     */
67
    private $isBaseWritten = false;
68
69
    /**
70
     * @param IService                $service Reference to the data service instance
71
     * @param RequestDescription|null $request Type instance describing the client submitted request
72
     * @throws \Exception
73
     */
74
    public function __construct(IService $service, RequestDescription $request = null)
75
    {
76
        $this->service = $service;
77
        $this->request = $request;
78
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
79
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
80
        $this->stack = new SegmentStack($request);
81
        $this->complexTypeInstanceCollection = [];
82
        $this->modelSerialiser = new ModelSerialiser();
83
        $this->updated = Carbon::now();
84
    }
85
86
    /**
87
     * Write a top level entry resource.
88
     *
89
     * @param QueryResult $entryObject Reference to the entry object to be written
90
     *
91
     * @return ODataEntry|null
92
     * @throws InvalidOperationException
93
     * @throws \ReflectionException
94
     * @throws ODataException
95
     */
96
    public function writeTopLevelElement(QueryResult $entryObject)
97
    {
98
        if (!isset($entryObject->results)) {
99
            array_pop($this->lightStack);
100
            return null;
101
        }
102
        if (!$entryObject->results instanceof Model) {
103
            $res = $entryObject->results;
104
            $msg = is_array($res) ? 'Entry object must be single Model' : get_class($res);
105
            throw new InvalidOperationException($msg);
106
        }
107
108
        $this->loadStackIfEmpty();
109
        $baseURI = $this->isBaseWritten ? null : $this->absoluteServiceUriWithSlash;
110
        $this->isBaseWritten = true;
111
112
        $stackCount = count($this->lightStack);
113
        $topOfStack = $this->lightStack[$stackCount-1];
114
        $payloadClass = get_class($entryObject->results);
115
        /** @var ResourceEntityType $resourceType */
116
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack['type']);
117
118
        // need gubbinz to unpack an abstract resource type
119
        $resourceType = $this->getConcreteTypeFromAbstractType($resourceType, $payloadClass);
120
121
        // make sure we're barking up right tree
122
        if (!$resourceType instanceof ResourceEntityType) {
123
            throw new InvalidOperationException(get_class($resourceType));
124
        }
125
126
        /** @var Model $res */
127
        $res = $entryObject->results;
128
        $targClass = $resourceType->getInstanceType()->getName();
129
        if (!($res instanceof $targClass)) {
130
            $msg = 'Object being serialised not instance of expected class, '
131
                   . $targClass . ', is actually ' . $payloadClass;
132
            throw new InvalidOperationException($msg);
133
        }
134
135
        $this->checkRelationPropertiesCached($targClass, $resourceType);
136
        /** @var ResourceProperty[] $relProp */
137
        $relProp = $this->propertiesCache[$targClass]['rel'];
138
        /** @var ResourceProperty[] $nonRelProp */
139
        $nonRelProp = $this->propertiesCache[$targClass]['nonRel'];
140
141
        $resourceSet = $resourceType->getCustomState();
142
        if (!$resourceSet instanceof ResourceSet) {
143
            throw new InvalidOperationException('');
144
        }
145
        $title = $resourceType->getName();
146
        $type = $resourceType->getFullName();
147
148
        $relativeUri = $this->getEntryInstanceKey(
149
            $res,
150
            $resourceType,
151
            $resourceSet->getName()
152
        );
153
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
154
155
        /** var $mediaLink ODataMediaLink|null */
156
        $mediaLink = null;
157
        /** var $mediaLinks ODataMediaLink[] */
158
        $mediaLinks = [];
159
        $this->writeMediaData(
160
            $res,
161
            $type,
162
            $relativeUri,
163
            $resourceType,
164
            $mediaLink,
165
            $mediaLinks
166
        );
167
168
        $propertyContent = $this->writePrimitiveProperties($res, $nonRelProp);
169
170
        $links = $this->buildLinksFromRels($entryObject, $relProp, $relativeUri);
171
172
        $odata = new ODataEntry();
173
        $odata->resourceSetName = $resourceSet->getName();
174
        $odata->id = $absoluteUri;
175
        $odata->title = new ODataTitle($title);
176
        $odata->type = new ODataCategory($type);
177
        $odata->propertyContent = $propertyContent;
178
        $odata->isMediaLinkEntry = $resourceType->isMediaLinkEntry();
179
        $odata->editLink = new ODataLink();
180
        $odata->editLink->url = $relativeUri;
181
        $odata->editLink->name = 'edit';
182
        $odata->editLink->title = $title;
183
        $odata->mediaLink = $mediaLink;
184
        $odata->mediaLinks = $mediaLinks;
185
        $odata->links = $links;
186
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
187
        $odata->baseURI = $baseURI;
188
189
        $newCount = count($this->lightStack);
190
        if ($newCount != $stackCount) {
191
            $msg = 'Should have ' . $stackCount . ' elements in stack, have ' . $newCount . ' elements';
192
            throw new InvalidOperationException($msg);
193
        }
194
        $this->lightStack[$newCount-1]['count']--;
195
        if (0 == $this->lightStack[$newCount-1]['count']) {
196
            array_pop($this->lightStack);
197
        }
198
        return $odata;
199
    }
200
201
    /**
202
     * Write top level feed element.
203
     *
204
     * @param QueryResult &$entryObjects Array of entry resources to be written
205
     *
206
     * @return ODataFeed
207
     * @throws InvalidOperationException
208
     * @throws ODataException
209
     * @throws \ReflectionException
210
     */
211
    public function writeTopLevelElements(QueryResult &$entryObjects)
212
    {
213
        $res = $entryObjects->results;
214
        if (!(is_array($res) || $res instanceof Collection)) {
215
            throw new InvalidOperationException('!is_array($entryObjects->results)');
216
        }
217
        if (is_array($res) && 0 == count($res)) {
218
            $entryObjects->hasMore = false;
219
        }
220
        if ($res instanceof Collection && 0 == $res->count()) {
221
            $entryObjects->hasMore = false;
222
        }
223
224
        $this->loadStackIfEmpty();
225
226
        $title = $this->getRequest()->getContainerName();
227
        $relativeUri = $this->getRequest()->getIdentifier();
228
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
229
230
        $selfLink = new ODataLink();
231
        $selfLink->name = 'self';
232
        $selfLink->title = $relativeUri;
233
        $selfLink->url = $relativeUri;
234
235
        $odata = new ODataFeed();
236
        $odata->title = new ODataTitle($title);
237
        $odata->id = $absoluteUri;
238
        $odata->selfLink = $selfLink;
239
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
240
        $odata->baseURI = $this->isBaseWritten ? null : $this->absoluteServiceUriWithSlash;
241
        $this->isBaseWritten = true;
242
243
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
244
            $odata->rowCount = $this->getRequest()->getCountValue();
245
        }
246
        $this->buildEntriesFromElements($res, $odata);
247
248
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
249
        $requestTop = $this->getRequest()->getTopOptionCount();
250
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
251
        $requestTop = (null === $requestTop) ? $pageSize+1 : $requestTop;
252
253
        if (true === $entryObjects->hasMore && $requestTop > $pageSize) {
254
            $this->buildNextPageLink($entryObjects, $odata);
255
        }
256
257
        return $odata;
258
    }
259
260
    /**
261
     * Write top level url element.
262
     *
263
     * @param QueryResult $entryObject The entry resource whose url to be written
264
     *
265
     * @return ODataURL
266
     * @throws InvalidOperationException
267
     * @throws ODataException
268
     * @throws \ReflectionException
269
     */
270
    public function writeUrlElement(QueryResult $entryObject)
271
    {
272
        $url = new ODataURL();
273
        /** @var Model|null $res */
274
        $res = $entryObject->results;
275
        if (null !== $res) {
276
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
277
            $relativeUri = $this->getEntryInstanceKey(
278
                $res,
279
                $currentResourceType,
280
                $this->getCurrentResourceSetWrapper()->getName()
281
            );
282
283
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
284
        }
285
286
        return $url;
287
    }
288
289
    /**
290
     * Write top level url collection.
291
     *
292
     * @param QueryResult $entryObjects Array of entry resources whose url to be written
293
     *
294
     * @return ODataURLCollection
295
     * @throws InvalidOperationException
296
     * @throws ODataException
297
     * @throws \ReflectionException
298
     */
299
    public function writeUrlElements(QueryResult $entryObjects)
300
    {
301
        $urls = new ODataURLCollection();
302
        if (!empty($entryObjects->results)) {
303
            $i = 0;
304
            foreach ($entryObjects->results as $entryObject) {
305
                if (!$entryObject instanceof QueryResult) {
306
                    $query = new QueryResult();
307
                    $query->results = $entryObject;
308
                } else {
309
                    $query = $entryObject;
310
                }
311
                $urls->urls[$i] = $this->writeUrlElement($query);
312
                ++$i;
313
            }
314
315
            if ($i > 0 && true === $entryObjects->hasMore) {
316
                $this->buildNextPageLink($entryObjects, $urls);
317
            }
318
        }
319
320
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
321
            $urls->count = $this->getRequest()->getCountValue();
322
        }
323
324
        return $urls;
325
    }
326
327
    /**
328
     * Write top level complex resource.
329
     *
330
     * @param QueryResult  &$complexValue The complex object to be written
331
     * @param string       $propertyName  The name of the complex property
332
     * @param ResourceType &$resourceType Describes the type of complex object
333
     *
334
     * @return ODataPropertyContent
335
     * @throws InvalidOperationException
336
     * @throws \ReflectionException
337
     */
338
    public function writeTopLevelComplexObject(QueryResult &$complexValue, $propertyName, ResourceType &$resourceType)
339
    {
340
        $result = $complexValue->results;
341
342
        $propertyContent = new ODataPropertyContent();
343
        $odataProperty = new ODataProperty();
344
        $odataProperty->name = $propertyName;
345
        $odataProperty->typeName = $resourceType->getFullName();
346
        if (null != $result) {
347
            $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

347
            $internalContent = $this->writeComplexValue($resourceType, /** @scrutinizer ignore-type */ $result);
Loading history...
348
            $odataProperty->value = $internalContent;
349
        }
350
351
        $propertyContent->properties[$propertyName] = $odataProperty;
352
353
        return $propertyContent;
354
    }
355
356
    /**
357
     * Write top level bag resource.
358
     *
359
     * @param QueryResult  &$BagValue     The bag object to be
360
     *                                    written
361
     * @param string       $propertyName  The name of the
362
     *                                    bag property
363
     * @param ResourceType &$resourceType Describes the type of
364
     *                                    bag object
365
     *
366
     * @return ODataPropertyContent
367
     * @throws InvalidOperationException
368
     * @throws \ReflectionException
369
     */
370
    public function writeTopLevelBagObject(QueryResult &$BagValue, $propertyName, ResourceType &$resourceType)
371
    {
372
        $result = $BagValue->results;
373
374
        $propertyContent = new ODataPropertyContent();
375
        $odataProperty = new ODataProperty();
376
        $odataProperty->name = $propertyName;
377
        $odataProperty->typeName = 'Collection(' . $resourceType->getFullName() . ')';
378
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
379
380
        $propertyContent->properties[$propertyName] = $odataProperty;
381
        return $propertyContent;
382
    }
383
384
    /**
385
     * Write top level primitive value.
386
     *
387
     * @param  QueryResult          &$primitiveValue   The primitive value to be
388
     *                                                 written
389
     * @param  ResourceProperty     &$resourceProperty Resource property describing the
390
     *                                                 primitive property to be written
391
     * @return ODataPropertyContent
392
     * @throws InvalidOperationException
393
     * @throws \ReflectionException
394
     */
395
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
396
    {
397
        if (null === $resourceProperty) {
398
            throw new InvalidOperationException('Resource property must not be null');
399
        }
400
        $propertyContent = new ODataPropertyContent();
401
402
        $odataProperty = new ODataProperty();
403
        $odataProperty->name = $resourceProperty->getName();
404
        $iType = $resourceProperty->getInstanceType();
405
        if (!$iType instanceof IType) {
406
            throw new InvalidOperationException(get_class($iType));
407
        }
408
        $odataProperty->typeName = $iType->getFullTypeName();
409
        if (null == $primitiveValue->results) {
410
            $odataProperty->value = null;
411
        } else {
412
            $rType = $resourceProperty->getResourceType()->getInstanceType();
413
            if (!$rType instanceof IType) {
414
                throw new InvalidOperationException(get_class($rType));
415
            }
416
            $odataProperty->value = $this->primitiveToString($rType, $primitiveValue->results);
417
        }
418
419
        $propertyContent->properties[$odataProperty->name] = $odataProperty;
420
421
        return $propertyContent;
422
    }
423
424
    /**
425
     * Get update timestamp.
426
     *
427
     * @return Carbon
428
     */
429
    public function getUpdated()
430
    {
431
        return $this->updated;
432
    }
433
434
    /**
435
     * @param Model $entityInstance
436
     * @param ResourceType $resourceType
437
     * @param string $containerName
438
     * @return string
439
     * @throws InvalidOperationException
440
     * @throws ODataException
441
     * @throws \ReflectionException
442
     */
443
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
444
    {
445
        $typeName = $resourceType->getName();
446
        $keyProperties = $resourceType->getKeyProperties();
447
        if (0 == count($keyProperties)) {
448
            throw new InvalidOperationException('count($keyProperties) == 0');
449
        }
450
        $keyString = $containerName . '(';
451
        $comma = null;
452
        foreach ($keyProperties as $keyName => $resourceProperty) {
453
            $keyType = $resourceProperty->getInstanceType();
454
            if (!$keyType instanceof IType) {
455
                throw new InvalidOperationException('$keyType not instanceof IType');
456
            }
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 $resourceType
480
     * @param ODataMediaLink|null $mediaLink
481
     * @param ODataMediaLink[] $mediaLinks
482
     * @return void
483
     * @throws InvalidOperationException
484
     */
485
    protected function writeMediaData(
486
        $entryObject,
487
        $type,
488
        $relativeUri,
489
        ResourceType $resourceType,
490
        ODataMediaLink &$mediaLink = null,
491
        array &$mediaLinks = []
492
    ) {
493
        $context = $this->getService()->getOperationContext();
494
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
495
        if (null == $streamProviderWrapper) {
496
            throw new InvalidOperationException('Retrieved stream provider must not be null');
497
        }
498
499
        /** @var ODataMediaLink|null $mediaLink */
500
        $mediaLink = null;
501
        if ($resourceType->isMediaLinkEntry()) {
502
            $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

502
            /** @scrutinizer ignore-call */ 
503
            $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...
503
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag, 'edit-media');
504
        }
505
        /** @var ODataMediaLink[] $mediaLinks */
506
        $mediaLinks = [];
507
        if ($resourceType->hasNamedStream()) {
508
            $namedStreams = $resourceType->getAllNamedStreams();
509
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
510
                $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

510
                /** @scrutinizer ignore-call */ 
511
                $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...
511
                    $entryObject,
512
                    $resourceStreamInfo,
513
                    $context,
514
                    $relativeUri
515
                );
516
                $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

516
                /** @scrutinizer ignore-call */ 
517
                $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...
517
                    $entryObject,
518
                    $resourceStreamInfo,
519
                    $context
520
                );
521
                $eTag = $streamProviderWrapper->getStreamETag2(
522
                    $entryObject,
523
                    $resourceStreamInfo,
524
                    $context
525
                );
526
527
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
528
                $mediaLinks[] = $nuLink;
529
            }
530
        }
531
    }
532
533
    /**
534
     * @param QueryResult $entryObject
535
     * @param ResourceProperty $prop
536
     * @param $nuLink
537
     * @param $propKind
538
     * @param $propName
539
     * @throws InvalidOperationException
540
     * @throws ODataException
541
     * @throws \ReflectionException
542
     */
543
    private function expandNavigationProperty(
544
        QueryResult $entryObject,
545
        ResourceProperty $prop,
546
        $nuLink,
547
        $propKind,
548
        $propName
549
    ) {
550
        $nextName = $prop->getResourceType()->getName();
551
        $nuLink->isExpanded = true;
552
        $value = $entryObject->results->$propName;
553
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
554
        $nuLink->isCollection = $isCollection;
555
556
        if (is_array($value)) {
557
            if (1 == count($value) && !$isCollection) {
558
                $value = $value[0];
559
            } else {
560
                $value = collect($value);
561
            }
562
        }
563
564
        $result = new QueryResult();
565
        $result->results = $value;
566
        $nullResult = null === $value;
567
        $isSingleton = $value instanceof Model;
568
        $resultCount = $nullResult ? 0 : ($isSingleton ? 1 : $value->count());
569
570
        if (0 < $resultCount) {
571
            $newStackLine = ['type' => $nextName, 'prop' => $propName, 'count' => $resultCount];
572
            array_push($this->lightStack, $newStackLine);
573
            if (!$isCollection) {
574
                $nuLink->type = 'application/atom+xml;type=entry';
575
                $expandedResult = $this->writeTopLevelElement($result);
576
            } else {
577
                $nuLink->type = 'application/atom+xml;type=feed';
578
                $expandedResult = $this->writeTopLevelElements($result);
579
            }
580
            $nuLink->expandedResult = $expandedResult;
581
        } else {
582
            $type = $this->getService()->getProvidersWrapper()->resolveResourceType($nextName);
583
            if (!$isCollection) {
584
                $result = new ODataEntry();
585
                $result->resourceSetName = $type->getName();
586
            } else {
587
                $result = new ODataFeed();
588
                $result->selfLink = new ODataLink();
589
                $result->selfLink->name = ODataConstants::ATOM_SELF_RELATION_ATTRIBUTE_VALUE;
590
            }
591
            $nuLink->expandedResult = $result;
592
        }
593
        if (isset($nuLink->expandedResult->selfLink)) {
594
            $nuLink->expandedResult->selfLink->title = $propName;
595
            $nuLink->expandedResult->selfLink->url = $nuLink->url;
596
            $nuLink->expandedResult->title = new ODataTitle($propName);
597
            $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
598
        }
599
        if (!isset($nuLink->expandedResult)) {
600
            throw new InvalidOperationException('');
601
        }
602
    }
603
604
    public static function isMatchPrimitive($resourceKind)
605
    {
606
        if (16 > $resourceKind) {
607
            return false;
608
        }
609
        if (28 < $resourceKind) {
610
            return false;
611
        }
612
        return 0 == ($resourceKind % 4);
613
    }
614
615
    /**
616
     * @param ResourceEntityType $resourceType
617
     * @param $payloadClass
618
     * @return ResourceEntityType|ResourceType
619
     * @throws InvalidOperationException
620
     * @throws \ReflectionException
621
     */
622
    protected function getConcreteTypeFromAbstractType(ResourceEntityType $resourceType, $payloadClass)
623
    {
624
        if ($resourceType->isAbstract()) {
625
            $derived = $this->getMetadata()->getDerivedTypes($resourceType);
626
            if (0 == count($derived)) {
627
                throw new InvalidOperationException('Supplied abstract type must have at least one derived type');
628
            }
629
            $derived = array_filter(
630
                $derived,
631
                function (ResourceType $element) {
632
                    return !$element->isAbstract();
633
                }
634
            );
635
            foreach ($derived as $rawType) {
636
                $name = $rawType->getInstanceType()->getName();
637
                if ($payloadClass == $name) {
638
                    $resourceType = $rawType;
639
                    break;
640
                }
641
            }
642
        }
643
        // despite all set up, checking, etc, if we haven't picked a concrete resource type,
644
        // wheels have fallen off, so blow up
645
        if ($resourceType->isAbstract()) {
646
            throw new InvalidOperationException('Concrete resource type not selected for payload ' . $payloadClass);
647
        }
648
        return $resourceType;
649
    }
650
651
    /**
652
     * @param QueryResult $entryObject
653
     * @param array $relProp
654
     * @param $relativeUri
655
     * @return array
656
     * @throws InvalidOperationException
657
     * @throws ODataException
658
     * @throws \ReflectionException
659
     */
660
    protected function buildLinksFromRels(QueryResult $entryObject, array $relProp, $relativeUri)
661
    {
662
        $links = [];
663
        foreach ($relProp as $prop) {
664
            $nuLink = new ODataLink();
665
            $propKind = $prop->getKind();
666
667
            if (!(ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
668
                  || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind)) {
669
                $msg = '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
670
                       . ' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE';
671
                throw new InvalidOperationException($msg);
672
            }
673
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
674
            $propType = 'application/atom+xml;type=' . $propTail;
675
            $propName = $prop->getName();
676
            $nuLink->title = $propName;
677
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
678
            $nuLink->url = $relativeUri . '/' . $propName;
679
            $nuLink->type = $propType;
680
            $nuLink->isExpanded = false;
681
            $nuLink->isCollection = 'feed' === $propTail;
682
683
            $shouldExpand = $this->shouldExpandSegment($propName);
684
685
            $navProp = new ODataNavigationPropertyInfo($prop, $shouldExpand);
686
            if ($navProp->expanded) {
687
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
688
            }
689
            $nuLink->isExpanded = isset($nuLink->expandedResult);
690
            if (null === $nuLink->isCollection) {
691
                throw new InvalidOperationException('');
692
            }
693
694
            $links[] = $nuLink;
695
        }
696
        return $links;
697
    }
698
699
    /**
700
     * @param $res
701
     * @param ODataFeed $odata
702
     * @throws InvalidOperationException
703
     * @throws ODataException
704
     * @throws \ReflectionException
705
     */
706
    protected function buildEntriesFromElements($res, ODataFeed $odata)
707
    {
708
        foreach ($res as $entry) {
709
            if (!$entry instanceof QueryResult) {
710
                $query = new QueryResult();
711
                $query->results = $entry;
712
            } else {
713
                $query = $entry;
714
            }
715
            $odata->entries[] = $this->writeTopLevelElement($query);
716
        }
717
    }
718
}
719