Passed
Pull Request — master (#206)
by Alex
06:35 queued 01:20
created

IronicSerialiser::getEntryInstanceKey()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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

334
            $internalContent = $this->writeComplexValue($resourceType, /** @scrutinizer ignore-type */ $result);
Loading history...
335
            $odataProperty->value = $internalContent;
336
        }
337
338
        $propertyContent->properties[$propertyName] = $odataProperty;
339
340
        return $propertyContent;
341
    }
342
343
    /**
344
     * Write top level bag resource.
345
     *
346
     * @param QueryResult  &$BagValue     The bag object to be
347
     *                                    written
348
     * @param string       $propertyName  The name of the
349
     *                                    bag property
350
     * @param ResourceType &$resourceType Describes the type of
351
     *                                    bag object
352
     *
353
     * @return ODataPropertyContent
354
     * @throws InvalidOperationException
355
     * @throws \ReflectionException
356
     */
357
    public function writeTopLevelBagObject(QueryResult &$BagValue, $propertyName, ResourceType &$resourceType)
358
    {
359
        $result = $BagValue->results;
360
361
        $propertyContent = new ODataPropertyContent();
362
        $odataProperty = new ODataProperty();
363
        $odataProperty->name = $propertyName;
364
        $odataProperty->typeName = 'Collection(' . $resourceType->getFullName() . ')';
365
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
366
367
        $propertyContent->properties[$propertyName] = $odataProperty;
368
        return $propertyContent;
369
    }
370
371
    /**
372
     * Write top level primitive value.
373
     *
374
     * @param  QueryResult          &$primitiveValue   The primitive value to be
375
     *                                                 written
376
     * @param  ResourceProperty     &$resourceProperty Resource property describing the
377
     *                                                 primitive property to be written
378
     * @return ODataPropertyContent
379
     * @throws InvalidOperationException
380
     * @throws \ReflectionException
381
     */
382
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
383
    {
384
        if (null === $resourceProperty) {
385
            throw new InvalidOperationException('Resource property must not be null');
386
        }
387
        $propertyContent = new ODataPropertyContent();
388
389
        $odataProperty = new ODataProperty();
390
        $odataProperty->name = $resourceProperty->getName();
391
        $iType = $resourceProperty->getInstanceType();
392
        if (!$iType instanceof IType) {
393
            throw new InvalidOperationException(get_class($iType));
394
        }
395
        $odataProperty->typeName = $iType->getFullTypeName();
396
        if (null == $primitiveValue->results) {
397
            $odataProperty->value = null;
398
        } else {
399
            $rType = $resourceProperty->getResourceType()->getInstanceType();
400
            if (!$rType instanceof IType) {
401
                throw new InvalidOperationException(get_class($rType));
402
            }
403
            $odataProperty->value = $this->primitiveToString($rType, $primitiveValue->results);
404
        }
405
406
        $propertyContent->properties[$odataProperty->name] = $odataProperty;
407
408
        return $propertyContent;
409
    }
410
411
    /**
412
     * Get update timestamp.
413
     *
414
     * @return Carbon
415
     */
416
    public function getUpdated()
417
    {
418
        return $this->updated;
419
    }
420
421
    /**
422
     * @param $entryObject
423
     * @param $type
424
     * @param $relativeUri
425
     * @param ResourceType $resourceType
426
     * @param ODataMediaLink|null $mediaLink
427
     * @param ODataMediaLink[] $mediaLinks
428
     * @return void
429
     * @throws InvalidOperationException
430
     */
431
    protected function writeMediaData(
432
        $entryObject,
433
        $type,
434
        $relativeUri,
435
        ResourceType $resourceType,
436
        ODataMediaLink &$mediaLink = null,
437
        array &$mediaLinks = []
438
    ) {
439
        $context = $this->getService()->getOperationContext();
440
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
441
        if (null == $streamProviderWrapper) {
442
            throw new InvalidOperationException('Retrieved stream provider must not be null');
443
        }
444
445
        /** @var ODataMediaLink|null $mediaLink */
446
        $mediaLink = null;
447
        if ($resourceType->isMediaLinkEntry()) {
448
            $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

448
            /** @scrutinizer ignore-call */ 
449
            $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...
449
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag, 'edit-media');
450
        }
451
        /** @var ODataMediaLink[] $mediaLinks */
452
        $mediaLinks = [];
453
        if ($resourceType->hasNamedStream()) {
454
            $namedStreams = $resourceType->getAllNamedStreams();
455
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
456
                $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

456
                /** @scrutinizer ignore-call */ 
457
                $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...
457
                    $entryObject,
458
                    $resourceStreamInfo,
459
                    $context,
460
                    $relativeUri
461
                );
462
                $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

462
                /** @scrutinizer ignore-call */ 
463
                $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...
463
                    $entryObject,
464
                    $resourceStreamInfo,
465
                    $context
466
                );
467
                $eTag = $streamProviderWrapper->getStreamETag2(
468
                    $entryObject,
469
                    $resourceStreamInfo,
470
                    $context
471
                );
472
473
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
474
                $mediaLinks[] = $nuLink;
475
            }
476
        }
477
    }
478
479
    /**
480
     * @param QueryResult $entryObject
481
     * @param ResourceProperty $prop
482
     * @param $nuLink
483
     * @param $propKind
484
     * @param $propName
485
     * @throws InvalidOperationException
486
     * @throws ODataException
487
     * @throws \ReflectionException
488
     */
489
    private function expandNavigationProperty(
490
        QueryResult $entryObject,
491
        ResourceProperty $prop,
492
        $nuLink,
493
        $propKind,
494
        $propName
495
    ) {
496
        $nextName = $prop->getResourceType()->getName();
497
        $nuLink->isExpanded = true;
498
        $value = $entryObject->results->$propName;
499
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
500
        $nuLink->isCollection = $isCollection;
501
502
        if (is_array($value)) {
503
            if (1 == count($value) && !$isCollection) {
504
                $value = $value[0];
505
            } else {
506
                $value = collect($value);
507
            }
508
        }
509
510
        $result = new QueryResult();
511
        $result->results = $value;
512
        $nullResult = null === $value;
513
        $isSingleton = $value instanceof Model;
514
        $resultCount = $nullResult ? 0 : ($isSingleton ? 1 : $value->count());
515
516
        if (0 < $resultCount) {
517
            $newStackLine = ['type' => $nextName, 'prop' => $propName, 'count' => $resultCount];
518
            array_push($this->lightStack, $newStackLine);
519
            if (!$isCollection) {
520
                $nuLink->type = 'application/atom+xml;type=entry';
521
                $expandedResult = $this->writeTopLevelElement($result);
522
            } else {
523
                $nuLink->type = 'application/atom+xml;type=feed';
524
                $expandedResult = $this->writeTopLevelElements($result);
525
            }
526
            $nuLink->expandedResult = $expandedResult;
527
        } else {
528
            $type = $this->getService()->getProvidersWrapper()->resolveResourceType($nextName);
529
            if (!$isCollection) {
530
                $result = new ODataEntry();
531
                $result->resourceSetName = $type->getName();
532
            } else {
533
                $result = new ODataFeed();
534
                $result->selfLink = new ODataLink();
535
                $result->selfLink->name = ODataConstants::ATOM_SELF_RELATION_ATTRIBUTE_VALUE;
536
            }
537
            $nuLink->expandedResult = $result;
538
        }
539
        if (isset($nuLink->expandedResult->selfLink)) {
540
            $nuLink->expandedResult->selfLink->title = $propName;
541
            $nuLink->expandedResult->selfLink->url = $nuLink->url;
542
            $nuLink->expandedResult->title = new ODataTitle($propName);
543
            $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
544
        }
545
        if (!isset($nuLink->expandedResult)) {
546
            throw new InvalidOperationException('');
547
        }
548
    }
549
550
    /**
551
     * @param QueryResult $entryObject
552
     * @param array $relProp
553
     * @param $relativeUri
554
     * @return array
555
     * @throws InvalidOperationException
556
     * @throws ODataException
557
     * @throws \ReflectionException
558
     */
559
    protected function buildLinksFromRels(QueryResult $entryObject, array $relProp, $relativeUri)
560
    {
561
        $links = [];
562
        foreach ($relProp as $prop) {
563
            $nuLink = new ODataLink();
564
            $propKind = $prop->getKind();
565
566
            if (!(ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
567
                  || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind)) {
568
                $msg = '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
569
                       . ' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE';
570
                throw new InvalidOperationException($msg);
571
            }
572
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
573
            $propType = 'application/atom+xml;type=' . $propTail;
574
            $propName = $prop->getName();
575
            $nuLink->title = $propName;
576
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
577
            $nuLink->url = $relativeUri . '/' . $propName;
578
            $nuLink->type = $propType;
579
            $nuLink->isExpanded = false;
580
            $nuLink->isCollection = 'feed' === $propTail;
581
582
            $shouldExpand = $this->shouldExpandSegment($propName);
583
584
            $navProp = new ODataNavigationPropertyInfo($prop, $shouldExpand);
585
            if ($navProp->expanded) {
586
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
587
            }
588
            $nuLink->isExpanded = isset($nuLink->expandedResult);
589
            if (null === $nuLink->isCollection) {
590
                throw new InvalidOperationException('');
591
            }
592
593
            $links[] = $nuLink;
594
        }
595
        return $links;
596
    }
597
598
    /**
599
     * @param $res
600
     * @param ODataFeed $odata
601
     * @throws InvalidOperationException
602
     * @throws ODataException
603
     * @throws \ReflectionException
604
     */
605
    protected function buildEntriesFromElements($res, ODataFeed $odata)
606
    {
607
        foreach ($res as $entry) {
608
            if (!$entry instanceof QueryResult) {
609
                $query = new QueryResult();
610
                $query->results = $entry;
611
            } else {
612
                $query = $entry;
613
            }
614
            $odata->entries[] = $this->writeTopLevelElement($query);
615
        }
616
    }
617
}
618