Completed
Pull Request — master (#137)
by Alex
01:52
created

getConcreteTypeFromAbstractType()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 20
rs 8.8571
cc 5
eloc 12
nc 5
nop 2
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));
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);
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);
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
292
        $this->loadStackIfEmpty();
293
        $setName = $this->getRequest()->getTargetResourceSetWrapper()->getName();
294
295
        $title = $this->getRequest()->getContainerName();
296
        $relativeUri = $this->getRequest()->getIdentifier();
297
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
298
299
        $selfLink = new ODataLink();
300
        $selfLink->name = 'self';
301
        $selfLink->title = $relativeUri;
302
        $selfLink->url = $relativeUri;
303
304
        $odata = new ODataFeed();
305
        $odata->title = new ODataTitle($title);
306
        $odata->id = $absoluteUri;
307
        $odata->selfLink = $selfLink;
308
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
309
        $odata->baseURI = $this->isBaseWritten ? null : $this->absoluteServiceUriWithSlash;
310
        $this->isBaseWritten = true;
311
312
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
313
            $odata->rowCount = $this->getRequest()->getCountValue();
314
        }
315
        foreach ($res as $entry) {
316
            if (!$entry instanceof QueryResult) {
317
                $query = new QueryResult();
318
                $query->results = $entry;
319
            } else {
320
                $query = $entry;
321
            }
322
            assert($query instanceof QueryResult, get_class($query));
323
            assert($query->results instanceof Model, get_class($query->results));
324
            $odata->entries[] = $this->writeTopLevelElement($query);
325
        }
326
327
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
328
        $requestTop = $this->getRequest()->getTopOptionCount();
329
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
330
        $requestTop = (null === $requestTop) ? $pageSize+1 : $requestTop;
331
332
        if (true === $entryObjects->hasMore && $requestTop > $pageSize) {
333
            $stackSegment = $setName;
334
            $lastObject = end($entryObjects->results);
335
            $segment = $this->getNextLinkUri($lastObject);
336
            $nextLink = new ODataLink();
337
            $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
338
            $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
339
            $odata->nextPageLink = $nextLink;
340
        }
341
342
        return $odata;
343
    }
344
345
    /**
346
     * Write top level url element.
347
     *
348
     * @param QueryResult $entryObject The entry resource whose url to be written
349
     *
350
     * @return ODataURL
351
     */
352
    public function writeUrlElement(QueryResult $entryObject)
353
    {
354
        $url = new ODataURL();
355
        if (null !== $entryObject->results) {
356
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
357
            $relativeUri = $this->getEntryInstanceKey(
358
                $entryObject->results,
359
                $currentResourceType,
360
                $this->getCurrentResourceSetWrapper()->getName()
361
            );
362
363
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
364
        }
365
366
        return $url;
367
    }
368
369
    /**
370
     * Write top level url collection.
371
     *
372
     * @param QueryResult $entryObjects Array of entry resources whose url to be written
373
     *
374
     * @return ODataURLCollection
375
     */
376
    public function writeUrlElements(QueryResult $entryObjects)
377
    {
378
        $urls = new ODataURLCollection();
379
        if (!empty($entryObjects->results)) {
380
            $i = 0;
381
            foreach ($entryObjects->results as $entryObject) {
382
                if (!$entryObject instanceof QueryResult) {
383
                    $query = new QueryResult();
384
                    $query->results = $entryObject;
385
                } else {
386
                    $query = $entryObject;
387
                }
388
                $urls->urls[$i] = $this->writeUrlElement($query);
389
                ++$i;
390
            }
391
392
            if ($i > 0 && true === $entryObjects->hasMore) {
393
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
394
                $lastObject = end($entryObjects->results);
395
                $segment = $this->getNextLinkUri($lastObject);
396
                $nextLink = new ODataLink();
397
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
398
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
399
                $urls->nextPageLink = $nextLink;
400
            }
401
        }
402
403
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
404
            $urls->count = $this->getRequest()->getCountValue();
405
        }
406
407
        return $urls;
408
    }
409
410
    /**
411
     * Write top level complex resource.
412
     *
413
     * @param QueryResult  &$complexValue The complex object to be written
414
     * @param string       $propertyName  The name of the complex property
415
     * @param ResourceType &$resourceType Describes the type of complex object
416
     *
417
     * @return ODataPropertyContent
418
     */
419
    public function writeTopLevelComplexObject(QueryResult & $complexValue, $propertyName, ResourceType & $resourceType)
420
    {
421
        $result = $complexValue->results;
422
423
        $propertyContent = new ODataPropertyContent();
424
        $odataProperty = new ODataProperty();
425
        $odataProperty->name = $propertyName;
426
        $odataProperty->typeName = $resourceType->getFullName();
427
        if (null != $result) {
428
            $internalContent = $this->writeComplexValue($resourceType, $result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $complexValue->results on line 421 can also be of type array<integer,object>; however, AlgoWeb\PODataLaravel\Se...er::writeComplexValue() does only seem to accept object, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
429
            $odataProperty->value = $internalContent;
430
        }
431
432
        $propertyContent->properties[$propertyName] = $odataProperty;
433
434
        return $propertyContent;
435
    }
436
437
    /**
438
     * Write top level bag resource.
439
     *
440
     * @param QueryResult  &$BagValue     The bag object to be
441
     *                                    written
442
     * @param string       $propertyName  The name of the
443
     *                                    bag property
444
     * @param ResourceType &$resourceType Describes the type of
445
     *                                    bag object
446
     *
447
     * @return ODataPropertyContent
448
     */
449
    public function writeTopLevelBagObject(QueryResult & $BagValue, $propertyName, ResourceType & $resourceType)
0 ignored issues
show
Coding Style Naming introduced by
The parameter $BagValue is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
450
    {
451
        $result = $BagValue->results;
0 ignored issues
show
Coding Style introduced by
$BagValue does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
452
453
        $propertyContent = new ODataPropertyContent();
454
        $odataProperty = new ODataProperty();
455
        $odataProperty->name = $propertyName;
456
        $odataProperty->typeName = 'Collection(' . $resourceType->getFullName() . ')';
457
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
458
459
        $propertyContent->properties[$propertyName] = $odataProperty;
460
        return $propertyContent;
461
    }
462
463
    /**
464
     * Write top level primitive value.
465
     *
466
     * @param  QueryResult          &$primitiveValue   The primitive value to be
467
     *                                                 written
468
     * @param  ResourceProperty     &$resourceProperty Resource property describing the
469
     *                                                 primitive property to be written
470
     * @return ODataPropertyContent
471
     */
472
    public function writeTopLevelPrimitive(QueryResult & $primitiveValue, ResourceProperty & $resourceProperty = null)
473
    {
474
        assert(null != $resourceProperty, 'Resource property must not be null');
475
        $propertyContent = new ODataPropertyContent();
476
477
        $odataProperty = new ODataProperty();
478
        $odataProperty->name = $resourceProperty->getName();
479
        $iType = $resourceProperty->getInstanceType();
480
        assert($iType instanceof IType, get_class($iType));
481
        $odataProperty->typeName = $iType->getFullTypeName();
482
        if (null == $primitiveValue->results) {
483
            $odataProperty->value = null;
484
        } else {
485
            $rType = $resourceProperty->getResourceType()->getInstanceType();
486
            assert($rType instanceof IType, get_class($rType));
487
            $odataProperty->value = $this->primitiveToString($rType, $primitiveValue->results);
488
        }
489
490
        $propertyContent->properties[$odataProperty->name] = $odataProperty;
491
492
        return $propertyContent;
493
    }
494
495
    /**
496
     * Gets reference to the request submitted by client.
497
     *
498
     * @return RequestDescription
499
     */
500
    public function getRequest()
501
    {
502
        assert(null != $this->request, 'Request not yet set');
503
504
        return $this->request;
505
    }
506
507
    /**
508
     * Sets reference to the request submitted by client.
509
     *
510
     * @param RequestDescription $request
511
     */
512
    public function setRequest(RequestDescription $request)
513
    {
514
        $this->request = $request;
515
        $this->stack->setRequest($request);
516
    }
517
518
    /**
519
     * Gets the data service instance.
520
     *
521
     * @return IService
522
     */
523
    public function getService()
524
    {
525
        return $this->service;
526
    }
527
528
    /**
529
     * Gets the segment stack instance.
530
     *
531
     * @return SegmentStack
532
     */
533
    public function getStack()
534
    {
535
        return $this->stack;
536
    }
537
538
    /**
539
     * Get update timestamp.
540
     *
541
     * @return Carbon
542
     */
543
    public function getUpdated()
544
    {
545
        return $this->updated;
546
    }
547
548
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
549
    {
550
        $typeName = $resourceType->getName();
551
        $keyProperties = $resourceType->getKeyProperties();
552
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
553
        $keyString = $containerName . '(';
554
        $comma = null;
555
        foreach ($keyProperties as $keyName => $resourceProperty) {
556
            $keyType = $resourceProperty->getInstanceType();
557
            assert($keyType instanceof IType, '$keyType not instanceof IType');
558
            $keyName = $resourceProperty->getName();
559
            $keyValue = (self::PK == $keyName) ? $entityInstance->getKey() : $entityInstance->$keyName;
560
            if (!isset($keyValue)) {
561
                throw ODataException::createInternalServerError(
562
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
563
                );
564
            }
565
566
            $keyValue = $keyType->convertToOData($keyValue);
567
            $keyString .= $comma . $keyName . '=' . $keyValue;
568
            $comma = ',';
569
        }
570
571
        $keyString .= ')';
572
573
        return $keyString;
574
    }
575
576
    /**
577
     * @param $entryObject
578
     * @param $type
579
     * @param $relativeUri
580
     * @param $resourceType
581
     * @return array<ODataMediaLink|null|array>
582
     */
583
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
584
    {
585
        $context = $this->getService()->getOperationContext();
586
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
587
        assert(null != $streamProviderWrapper, 'Retrieved stream provider must not be null');
588
589
        $mediaLink = null;
590
        if ($resourceType->isMediaLinkEntry()) {
591
            $eTag = $streamProviderWrapper->getStreamETag2($entryObject, null, $context);
0 ignored issues
show
Bug introduced by
The method getStreamETag2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getStreamETag()?

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

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

Loading history...
592
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag, 'edit-media');
593
        }
594
        $mediaLinks = [];
595
        if ($resourceType->hasNamedStream()) {
596
            $namedStreams = $resourceType->getAllNamedStreams();
597
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
598
                $readUri = $streamProviderWrapper->getReadStreamUri2(
0 ignored issues
show
Bug introduced by
The method getReadStreamUri2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getReadStream()?

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

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

Loading history...
599
                    $entryObject,
600
                    $resourceStreamInfo,
601
                    $context,
602
                    $relativeUri
603
                );
604
                $mediaContentType = $streamProviderWrapper->getStreamContentType2(
0 ignored issues
show
Bug introduced by
The method getStreamContentType2() does not exist on POData\Providers\Stream\StreamProviderWrapper. Did you maybe mean getStreamContentType()?

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

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

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

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

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

Loading history...
610
                    $entryObject,
611
                    $resourceStreamInfo,
612
                    $context
613
                );
614
615
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
616
                $mediaLinks[] = $nuLink;
617
            }
618
        }
619
        return [$mediaLink, $mediaLinks];
620
    }
621
622
    /**
623
     * Gets collection of projection nodes under the current node.
624
     *
625
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes describing projections for the current
626
     *                                                        segment, If this method returns null it means no
627
     *                                                        projections are to be applied and the entire resource for
628
     *                                                        the current segment should be serialized, If it returns
629
     *                                                        non-null only the properties described by the returned
630
     *                                                        projection segments should be serialized
631
     */
632
    protected function getProjectionNodes()
633
    {
634
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
635
        if (null === $expandedProjectionNode || $expandedProjectionNode->canSelectAllProperties()) {
636
            return null;
637
        }
638
639
        return $expandedProjectionNode->getChildNodes();
640
    }
641
642
    /**
643
     * Find a 'ExpandedProjectionNode' instance in the projection tree
644
     * which describes the current segment.
645
     *
646
     * @return null|RootProjectionNode|ExpandedProjectionNode
647
     */
648
    protected function getCurrentExpandedProjectionNode()
649
    {
650
        if (null === $this->rootNode) {
651
            $this->rootNode = $this->getRequest()->getRootProjectionNode();
652
        }
653
        $expandedProjectionNode = $this->rootNode;
654
        if (null === $expandedProjectionNode) {
655
            return null;
656
        } else {
657
            $segmentNames = $this->getLightStack();
658
            $depth = count($segmentNames);
659
            // $depth == 1 means serialization of root entry
660
            //(the resource identified by resource path) is going on,
661
            //so control won't get into the below for loop.
662
            //we will directly return the root node,
663
            //which is 'ExpandedProjectionNode'
664
            // for resource identified by resource path.
665
            if (0 != $depth) {
666
                for ($i = 1; $i < $depth; ++$i) {
667
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]['prop']);
668
                    assert(null !== $expandedProjectionNode, 'is_null($expandedProjectionNode)');
669
                    assert(
670
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
671
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
672
                    );
673
                }
674
            }
675
        }
676
677
        return $expandedProjectionNode;
678
    }
679
680
    /**
681
     * Check whether to expand a navigation property or not.
682
     *
683
     * @param string $navigationPropertyName Name of naviagtion property in question
684
     *
685
     * @return bool True if the given navigation should be expanded, otherwise false
686
     */
687
    protected function shouldExpandSegment($navigationPropertyName)
688
    {
689
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
690
        if (null === $expandedProjectionNode) {
691
            return false;
692
        }
693
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
694
695
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
696
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
697
    }
698
699
    /**
700
     * Wheter next link is needed for the current resource set (feed)
701
     * being serialized.
702
     *
703
     * @param int $resultSetCount Number of entries in the current
704
     *                            resource set
705
     *
706
     * @return bool true if the feed must have a next page link
707
     */
708
    protected function needNextPageLink($resultSetCount)
709
    {
710
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
711
        $recursionLevel = count($this->getStack()->getSegmentNames());
712
        $pageSize = $currentResourceSet->getResourceSetPageSize();
713
714
        if (1 == $recursionLevel) {
715
            //presence of $top option affect next link for root container
716
            $topValueCount = $this->getRequest()->getTopOptionCount();
717
            if (null !== $topValueCount && ($topValueCount <= $pageSize)) {
718
                return false;
719
            }
720
        }
721
        return $resultSetCount == $pageSize;
722
    }
723
724
    /**
725
     * Resource set wrapper for the resource being serialized.
726
     *
727
     * @return ResourceSetWrapper
728
     */
729
    protected function getCurrentResourceSetWrapper()
730
    {
731
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
732
        $count = count($segmentWrappers);
733
734
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count-1];
735
    }
736
737
    /**
738
     * Get next page link from the given entity instance.
739
     *
740
     * @param  mixed          &$lastObject Last object serialized to be
741
     *                                     used for generating
742
     *                                     $skiptoken
743
     * @throws ODataException
744
     * @return string         for the link for next page
745
     */
746
    protected function getNextLinkUri(&$lastObject)
747
    {
748
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
749
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
750
        assert(null != $internalOrderByInfo);
751
        assert(is_object($internalOrderByInfo));
752
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
753
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
754
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
755
756
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
757
        assert(null !== $skipToken, '!is_null($skipToken)');
758
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
759
        $skipToken = (1 < $numSegments) ? $skipToken : intval(trim($skipToken, '\''));
760
        $skipToken = '?' . $queryParameterString . $token . $skipToken;
761
762
        return $skipToken;
763
    }
764
765
    /**
766
     * Builds the string corresponding to query parameters for top level results
767
     * (result set identified by the resource path) to be put in next page link.
768
     *
769
     * @return string|null string representing the query parameters in the URI
770
     *                     query parameter format, NULL if there
771
     *                     is no query parameters
772
     *                     required for the next link of top level result set
773
     */
774
    protected function getNextPageLinkQueryParametersForRootResourceSet()
775
    {
776
        $queryParameterString = null;
777
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
778
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
779
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
780
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
781
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
782
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
783
            if (null !== $value) {
784
                if (null !== $queryParameterString) {
785
                    $queryParameterString = $queryParameterString . '&';
786
                }
787
788
                $queryParameterString .= $queryOption . '=' . $value;
789
            }
790
        }
791
792
        $topCountValue = $this->getRequest()->getTopOptionCount();
793
        if (null !== $topCountValue) {
794
            $remainingCount = $topCountValue-$this->getRequest()->getTopCount();
795
            if (0 < $remainingCount) {
796
                if (null !== $queryParameterString) {
797
                    $queryParameterString .= '&';
798
                }
799
800
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
801
            }
802
        }
803
804
        if (null !== $queryParameterString) {
805
            $queryParameterString .= '&';
806
        }
807
808
        return $queryParameterString;
809
    }
810
811
    private function loadStackIfEmpty()
812
    {
813
        if (0 == count($this->lightStack)) {
814
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
815
            array_push($this->lightStack, ['type' => $typeName, 'property' => $typeName, 'count' => 1]);
816
        }
817
    }
818
819
    /**
820
     * Convert the given primitive value to string.
821
     * Note: This method will not handle null primitive value.
822
     *
823
     * @param IType &$primitiveResourceType Type of the primitive property
824
     *                                      whose value need to be converted
825
     * @param mixed $primitiveValue         Primitive value to convert
826
     *
827
     * @return string
828
     */
829
    private function primitiveToString(IType & $type, $primitiveValue)
830
    {
831
        // kludge to enable switching on type of $type without getting tripped up by mocks as we would with get_class
832
        // switch (true) means we unconditionally enter, and then lean on case statements to match given block
833
        switch (true) {
834
            case $type instanceof StringType:
835
                $stringValue = utf8_encode($primitiveValue);
836
                break;
837
            case $type instanceof Boolean:
838
                $stringValue = (true === $primitiveValue) ? 'true' : 'false';
839
                break;
840
            case $type instanceof Binary:
841
                $stringValue = base64_encode($primitiveValue);
842
                break;
843
            case $type instanceof DateTime && $primitiveValue instanceof \DateTime:
844
                $stringValue = $primitiveValue->format(\DateTime::ATOM);
845
                break;
846
            default:
847
                $stringValue = strval($primitiveValue);
848
        }
849
850
        return $stringValue;
851
    }
852
853
    /**
854
     * @param $entryObject
855
     * @param $nonRelProp
856
     * @return ODataPropertyContent
857
     */
858
    private function writePrimitiveProperties(Model $entryObject, $nonRelProp)
859
    {
860
        $propertyContent = new ODataPropertyContent();
861
        $cereal = $this->getModelSerialiser()->bulkSerialise($entryObject);
862
        foreach ($cereal as $corn => $flake) {
863
            if (!array_key_exists($corn, $nonRelProp)) {
864
                continue;
865
            }
866
            $corn = strval($corn);
867
            $rType = $nonRelProp[$corn]['type'];
868
            $subProp = new ODataProperty();
869
            $subProp->name = $corn;
870
            $subProp->value = isset($flake) ? $this->primitiveToString($rType, $flake) : null;
871
            $subProp->typeName = $nonRelProp[$corn]['prop']->getResourceType()->getFullName();
872
            $propertyContent->properties[$corn] = $subProp;
873
        }
874
        return $propertyContent;
875
    }
876
877
    /**
878
     * @param $entryObject
879
     * @param $prop
880
     * @param $nuLink
881
     * @param $propKind
882
     * @param $propName
883
     */
884
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
885
    {
886
        $nextName = $prop->getResourceType()->getName();
887
        $nuLink->isExpanded = true;
888
        $value = $entryObject->results->$propName;
889
        $isCombo = is_array($value) || $value instanceof Collection;
890
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind || $isCombo;
891
        $nuLink->isCollection = $isCollection;
892
893
        $result = new QueryResult();
894
        $result->results = $value;
895
        $resultCount = $isCollection ? count($value) : 1;
896
897
        if (0 < $resultCount) {
898
            $newStackLine = ['type' => $nextName, 'prop' => $propName, 'count' => $resultCount];
899
            array_push($this->lightStack, $newStackLine);
900
            if (!$isCollection) {
901
                $nuLink->type = 'application/atom+xml;type=entry';
902
                $expandedResult = $this->writeTopLevelElement($result);
903
            } else {
904
                $nuLink->type = 'application/atom+xml;type=feed';
905
                $expandedResult = $this->writeTopLevelElements($result);
906
            }
907
            $nuLink->expandedResult = $expandedResult;
908
        }
909
        if (!isset($nuLink->expandedResult)) {
910
            $nuLink->isCollection = null;
911
            $nuLink->isExpanded = null;
912
        } else {
913
            if (isset($nuLink->expandedResult->selfLink)) {
914
                $nuLink->expandedResult->selfLink->title = $propName;
915
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
916
                $nuLink->expandedResult->title = new ODataTitle($propName);
917
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
918
            }
919
        }
920
    }
921
922
    /**
923
     * Gets the data service instance.
924
     *
925
     * @return void
926
     */
927
    public function setService(IService $service)
928
    {
929
        $this->service = $service;
930
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
931
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
932
    }
933
934
    /**
935
     * @param ResourceType $resourceType
936
     * @param $result
937
     * @return ODataBagContent|null
938
     */
939
    protected function writeBagValue(ResourceType & $resourceType, $result)
940
    {
941
        assert(null == $result || is_array($result), 'Bag parameter must be null or array');
942
        $typeKind = $resourceType->getResourceTypeKind();
943
        assert(
944
            ResourceTypeKind::PRIMITIVE() == $typeKind || ResourceTypeKind::COMPLEX() == $typeKind,
945
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
946
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
947
        );
948
        if (null == $result) {
949
            return null;
950
        }
951
        $bag = new ODataBagContent();
952
        foreach ($result as $value) {
953
            if (isset($value)) {
954
                if (ResourceTypeKind::PRIMITIVE() == $typeKind) {
955
                    $instance = $resourceType->getInstanceType();
956
                    assert($instance instanceof IType, get_class($instance));
957
                    $bag->propertyContents[] = $this->primitiveToString($instance, $value);
958
                } elseif (ResourceTypeKind::COMPLEX() == $typeKind) {
959
                    $bag->propertyContents[] = $this->writeComplexValue($resourceType, $value);
960
                }
961
            }
962
        }
963
        return $bag;
964
    }
965
966
    /**
967
     * @param  ResourceType         $resourceType
968
     * @param  object               $result
969
     * @param  string|null          $propertyName
970
     * @return ODataPropertyContent
971
     */
972
    protected function writeComplexValue(ResourceType & $resourceType, &$result, $propertyName = null)
973
    {
974
        assert(is_object($result), 'Supplied $customObject must be an object');
975
976
        $count = count($this->complexTypeInstanceCollection);
977
        for ($i = 0; $i < $count; ++$i) {
978
            if ($this->complexTypeInstanceCollection[$i] === $result) {
979
                throw new InvalidOperationException(
980
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
981
                );
982
            }
983
        }
984
985
        $this->complexTypeInstanceCollection[$count] = &$result;
986
987
        $internalContent = new ODataPropertyContent();
988
        $resourceProperties = $resourceType->getAllProperties();
989
        // first up, handle primitive properties
990
        foreach ($resourceProperties as $prop) {
991
            $resourceKind = $prop->getKind();
992
            $propName = $prop->getName();
993
            $internalProperty = new ODataProperty();
994
            $internalProperty->name = $propName;
995
            if (static::isMatchPrimitive($resourceKind)) {
996
                $iType = $prop->getInstanceType();
997
                assert($iType instanceof IType, get_class($iType));
998
                $internalProperty->typeName = $iType->getFullTypeName();
999
1000
                $rType = $prop->getResourceType()->getInstanceType();
1001
                assert($rType instanceof IType, get_class($rType));
1002
                $internalProperty->value = $this->primitiveToString($rType, $result->$propName);
1003
1004
                $internalContent->properties[$propName] = $internalProperty;
1005
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
1006
                $rType = $prop->getResourceType();
1007
                $internalProperty->typeName = $rType->getFullName();
1008
                $internalProperty->value = $this->writeComplexValue($rType, $result->$propName, $propName);
1009
1010
                $internalContent->properties[$propName] = $internalProperty;
1011
            }
1012
        }
1013
1014
        unset($this->complexTypeInstanceCollection[$count]);
1015
        return $internalContent;
1016
    }
1017
1018
    public static function isMatchPrimitive($resourceKind)
1019
    {
1020
        if (16 > $resourceKind) {
1021
            return false;
1022
        }
1023
        if (28 < $resourceKind) {
1024
            return false;
1025
        }
1026
        return 0 == ($resourceKind % 4);
1027
    }
1028
1029
    /*
1030
     * @return IMetadataProvider
1031
     */
1032
    protected function getMetadata()
1033
    {
1034
        if (null == $this->metaProvider) {
1035
            $this->metaProvider = App::make('metadata');
1036
        }
1037
        return $this->metaProvider;
1038
    }
1039
1040
    /**
1041
     * @return array
1042
     */
1043
    protected function getLightStack()
1044
    {
1045
        return $this->lightStack;
1046
    }
1047
1048
    /**
1049
     * @return ModelSerialiser
1050
     */
1051
    public function getModelSerialiser()
1052
    {
1053
        return $this->modelSerialiser;
1054
    }
1055
1056
    protected function getConcreteTypeFromAbstractType(ResourceEntityType $resourceType, $payloadClass)
1057
    {
1058
        if ($resourceType->isAbstract()) {
1059
            $derived = $this->getMetadata()->getDerivedTypes($resourceType);
1060
            assert(0 < count($derived), 'Supplied abstract type must have at least one derived type');
1061
            foreach ($derived as $rawType) {
1062
                if (!$rawType->isAbstract()) {
1063
                    $name = $rawType->getInstanceType()->getName();
1064
                    if ($payloadClass == $name) {
1065
                        $resourceType = $rawType;
1066
                        break;
1067
                    }
1068
                }
1069
            }
1070
        }
1071
        // despite all set up, checking, etc, if we haven't picked a concrete resource type,
1072
        // wheels have fallen off, so blow up
1073
        assert(!$resourceType->isAbstract(), 'Concrete resource type not selected for payload ' . $payloadClass);
1074
        return $resourceType;
1075
    }
1076
}
1077