Test Failed
Pull Request — master (#94)
by Alex
03:36
created

IronicSerialiser   F

Complexity

Total Complexity 111

Size/Duplication

Total Lines 945
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 32

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 111
c 9
b 0
f 0
lcom 1
cbo 32
dl 0
loc 945
rs 1.0434

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
D writeTopLevelElement() 0 121 13
C writeTopLevelElements() 0 51 7
A writeUrlElement() 0 16 2
B writeUrlElements() 0 27 6
A writeTopLevelComplexObject() 0 17 2
A writeTopLevelBagObject() 0 13 1
A writeTopLevelPrimitive() 0 22 2
A getRequest() 0 6 1
A setRequest() 0 5 1
A getService() 0 4 1
A getStack() 0 4 1
B getEntryInstanceKey() 0 27 4
B writeMediaData() 0 38 4
A getProjectionNodes() 0 9 3
B getCurrentExpandedProjectionNode() 0 28 4
A shouldExpandSegment() 0 11 2
A needNextPageLink() 0 15 4
A getCurrentResourceSetWrapper() 0 7 2
A getNextLinkUri() 0 17 2
C getNextPageLinkQueryParametersForRootResourceSet() 0 36 8
A loadStackIfEmpty() 0 7 2
B primitiveToString() 0 16 7
A writePrimitiveProperties() 0 18 4
B expandNavigationProperty() 0 32 5
A setService() 0 6 1
C writeBagValue() 0 26 8
B writeComplexValue() 0 45 6
A isMatchPrimitive() 0 10 3
A getMetadata() 0 7 2
A getLightStack() 0 4 1
A getModelSerialiser() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like IronicSerialiser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
389
            $odataProperty->value = $internalContent;
390
        }
391
392
        $propertyContent->properties[] = $odataProperty;
393
394
        return $propertyContent;
395
    }
396
397
    /**
398
     * Write top level bag resource.
399
     *
400
     * @param QueryResult &$BagValue The bag object to be
401
     *                                    written
402
     * @param string $propertyName The name of the
403
     *                                    bag property
404
     * @param ResourceType &$resourceType Describes the type of
405
     *                                    bag object
406
     * @return ODataPropertyContent
407
     */
408
    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...
409
    {
410
        $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...
411
412
        $propertyContent = new ODataPropertyContent();
413
        $odataProperty = new ODataProperty();
414
        $odataProperty->name = $propertyName;
415
        $odataProperty->typeName = 'Collection('.$resourceType->getFullName().')';
416
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
417
418
        $propertyContent->properties[] = $odataProperty;
419
        return $propertyContent;
420
    }
421
422
    /**
423
     * Write top level primitive value.
424
     *
425
     * @param QueryResult &$primitiveValue              The primitive value to be
426
     *                                            written
427
     * @param ResourceProperty &$resourceProperty Resource property describing the
428
     *                                            primitive property to be written
429
     * @return ODataPropertyContent
430
     */
431
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
432
    {
433
        assert(null != $resourceProperty, 'Resource property must not be null');
434
        $propertyContent = new ODataPropertyContent();
435
436
        $odataProperty = new ODataProperty();
437
        $odataProperty->name = $resourceProperty->getName();
438
        $iType = $resourceProperty->getInstanceType();
439
        assert($iType instanceof IType, get_class($iType));
440
        $odataProperty->typeName = $iType->getFullTypeName();
441
        if (null == $primitiveValue->results) {
442
            $odataProperty->value = null;
443
        } else {
444
            $rType = $resourceProperty->getResourceType()->getInstanceType();
445
            assert($rType instanceof IType, get_class($rType));
446
            $odataProperty->value = $this->primitiveToString($rType, $primitiveValue->results);
447
        }
448
449
        $propertyContent->properties[] = $odataProperty;
450
451
        return $propertyContent;
452
    }
453
454
    /**
455
     * Gets reference to the request submitted by client.
456
     *
457
     * @return RequestDescription
458
     */
459
    public function getRequest()
460
    {
461
        assert(null != $this->request, 'Request not yet set');
462
463
        return $this->request;
464
    }
465
466
    /**
467
     * Sets reference to the request submitted by client.
468
     *
469
     * @param RequestDescription $request
470
     */
471
    public function setRequest(RequestDescription $request)
472
    {
473
        $this->request = $request;
474
        $this->stack->setRequest($request);
475
    }
476
477
    /**
478
     * Gets the data service instance.
479
     *
480
     * @return IService
481
     */
482
    public function getService()
483
    {
484
        return $this->service;
485
    }
486
487
    /**
488
     * Gets the segment stack instance.
489
     *
490
     * @return SegmentStack
491
     */
492
    public function getStack()
493
    {
494
        return $this->stack;
495
    }
496
497
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
498
    {
499
        $typeName = $resourceType->getName();
500
        $keyProperties = $resourceType->getKeyProperties();
501
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
502
        $keyString = $containerName . '(';
503
        $comma = null;
504
        foreach ($keyProperties as $keyName => $resourceProperty) {
505
            $keyType = $resourceProperty->getInstanceType();
506
            assert($keyType instanceof IType, '$keyType not instanceof IType');
507
            $keyName = $resourceProperty->getName();
508
            $keyValue = (self::PK == $keyName) ? $entityInstance->getKey() : $entityInstance->$keyName;
509
            if (!isset($keyValue)) {
510
                throw ODataException::createInternalServerError(
511
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
512
                );
513
            }
514
515
            $keyValue = $keyType->convertToOData($keyValue);
516
            $keyString .= $comma . $keyName . '=' . $keyValue;
517
            $comma = ',';
518
        }
519
520
        $keyString .= ')';
521
522
        return $keyString;
523
    }
524
525
    /**
526
     * @param $entryObject
527
     * @param $type
528
     * @param $relativeUri
529
     * @param $resourceType
530
     * @return array<ODataMediaLink|null|array>
531
     */
532
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
533
    {
534
        $context = $this->getService()->getOperationContext();
535
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
536
        assert(null != $streamProviderWrapper, 'Retrieved stream provider must not be null');
537
538
        $mediaLink = null;
539
        if ($resourceType->isMediaLinkEntry()) {
540
            $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...
541
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag);
542
        }
543
        $mediaLinks = [];
544
        if ($resourceType->hasNamedStream()) {
545
            $namedStreams = $resourceType->getAllNamedStreams();
546
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
547
                $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...
548
                    $entryObject,
549
                    $resourceStreamInfo,
550
                    $context,
551
                    $relativeUri
552
                );
553
                $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...
554
                    $entryObject,
555
                    $resourceStreamInfo,
556
                    $context
557
                );
558
                $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...
559
                    $entryObject,
560
                    $resourceStreamInfo,
561
                    $context
562
                );
563
564
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
565
                $mediaLinks[] = $nuLink;
566
            }
567
        }
568
        return [$mediaLink, $mediaLinks];
569
    }
570
571
    /**
572
     * Gets collection of projection nodes under the current node.
573
     *
574
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes describing projections for the current
575
     *                                                        segment, If this method returns null it means no
576
     *                                                        projections are to be applied and the entire resource for
577
     *                                                        the current segment should be serialized, If it returns
578
     *                                                        non-null only the properties described by the returned
579
     *                                                        projection segments should be serialized
580
     */
581
    protected function getProjectionNodes()
582
    {
583
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
584
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
585
            return null;
586
        }
587
588
        return $expandedProjectionNode->getChildNodes();
589
    }
590
591
    /**
592
     * Find a 'ExpandedProjectionNode' instance in the projection tree
593
     * which describes the current segment.
594
     *
595
     * @return null|RootProjectionNode|ExpandedProjectionNode
596
     */
597
    protected function getCurrentExpandedProjectionNode()
598
    {
599
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
600
        if (is_null($expandedProjectionNode)) {
601
            return null;
602
        } else {
603
            $segmentNames = $this->getLightStack();
604
            $depth = count($segmentNames);
605
            // $depth == 1 means serialization of root entry
606
            //(the resource identified by resource path) is going on,
607
            //so control won't get into the below for loop.
608
            //we will directly return the root node,
609
            //which is 'ExpandedProjectionNode'
610
            // for resource identified by resource path.
611
            if (0 != $depth) {
612
                for ($i = 1; $i < $depth; ++$i) {
613
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]['prop']);
614
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
615
                    assert(
616
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
617
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
618
                    );
619
                }
620
            }
621
        }
622
623
        return $expandedProjectionNode;
624
    }
625
626
    /**
627
     * Check whether to expand a navigation property or not.
628
     *
629
     * @param string $navigationPropertyName Name of naviagtion property in question
630
     *
631
     * @return bool True if the given navigation should be expanded, otherwise false
632
     */
633
    protected function shouldExpandSegment($navigationPropertyName)
634
    {
635
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
636
        if (is_null($expandedProjectionNode)) {
637
            return false;
638
        }
639
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
640
641
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
642
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
643
    }
644
645
    /**
646
     * Wheter next link is needed for the current resource set (feed)
647
     * being serialized.
648
     *
649
     * @param int $resultSetCount Number of entries in the current
650
     *                            resource set
651
     *
652
     * @return bool true if the feed must have a next page link
653
     */
654
    protected function needNextPageLink($resultSetCount)
655
    {
656
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
657
        $recursionLevel = count($this->getStack()->getSegmentNames());
658
        $pageSize = $currentResourceSet->getResourceSetPageSize();
659
660
        if (1 == $recursionLevel) {
661
            //presence of $top option affect next link for root container
662
            $topValueCount = $this->getRequest()->getTopOptionCount();
663
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
664
                return false;
665
            }
666
        }
667
        return $resultSetCount == $pageSize;
668
    }
669
670
    /**
671
     * Resource set wrapper for the resource being serialized.
672
     *
673
     * @return ResourceSetWrapper
674
     */
675
    protected function getCurrentResourceSetWrapper()
676
    {
677
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
678
        $count = count($segmentWrappers);
679
680
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
681
    }
682
683
    /**
684
     * Get next page link from the given entity instance.
685
     *
686
     * @param mixed  &$lastObject Last object serialized to be
687
     *                            used for generating $skiptoken
688
     * @param string $absoluteUri Absolute response URI
689
     *
690
     * @return string for the link for next page
691
     */
692
    protected function getNextLinkUri(&$lastObject, $absoluteUri)
0 ignored issues
show
Unused Code introduced by
The parameter $absoluteUri is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
693
    {
694
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
695
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
696
        assert(null != $internalOrderByInfo);
697
        assert(is_object($internalOrderByInfo));
698
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
699
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
700
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
701
702
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
703
        assert(!is_null($skipToken), '!is_null($skipToken)');
704
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
705
        $skipToken = '?'.$queryParameterString.$token.$skipToken;
706
707
        return $skipToken;
708
    }
709
710
    /**
711
     * Builds the string corresponding to query parameters for top level results
712
     * (result set identified by the resource path) to be put in next page link.
713
     *
714
     * @return string|null string representing the query parameters in the URI
715
     *                     query parameter format, NULL if there
716
     *                     is no query parameters
717
     *                     required for the next link of top level result set
718
     */
719
    protected function getNextPageLinkQueryParametersForRootResourceSet()
720
    {
721
        $queryParameterString = null;
722
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
723
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
724
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
725
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
726
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
727
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
728
            if (!is_null($value)) {
729
                if (!is_null($queryParameterString)) {
730
                    $queryParameterString = $queryParameterString . '&';
731
                }
732
733
                $queryParameterString .= $queryOption . '=' . $value;
734
            }
735
        }
736
737
        $topCountValue = $this->getRequest()->getTopOptionCount();
738
        if (!is_null($topCountValue)) {
739
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
740
            if (0 < $remainingCount) {
741
                if (!is_null($queryParameterString)) {
742
                    $queryParameterString .= '&';
743
                }
744
745
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
746
            }
747
        }
748
749
        if (!is_null($queryParameterString)) {
750
            $queryParameterString .= '&';
751
        }
752
753
        return $queryParameterString;
754
    }
755
756
    private function loadStackIfEmpty()
757
    {
758
        if (0 == count($this->lightStack)) {
759
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
760
            array_push($this->lightStack, ['type' => $typeName, 'property' => $typeName, 'count' => 1]);
761
        }
762
    }
763
764
    /**
765
     * Convert the given primitive value to string.
766
     * Note: This method will not handle null primitive value.
767
     *
768
     * @param IType &$primitiveResourceType        Type of the primitive property
769
     *                                             whose value need to be converted
770
     * @param mixed        $primitiveValue         Primitive value to convert
771
     *
772
     * @return string
773
     */
774
    private function primitiveToString(IType &$type, $primitiveValue)
775
    {
776
        if ($type instanceof Boolean) {
777
            $stringValue = (true === $primitiveValue) ? 'true' : 'false';
778
        } elseif ($type instanceof Binary) {
779
            $stringValue = base64_encode($primitiveValue);
780
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
781
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
782
        } elseif ($type instanceof StringType) {
783
            $stringValue = utf8_encode($primitiveValue);
784
        } else {
785
            $stringValue = strval($primitiveValue);
786
        }
787
788
        return $stringValue;
789
    }
790
791
    /**
792
     * @param $entryObject
793
     * @param $nonRelProp
794
     * @return ODataPropertyContent
795
     */
796
    private function writePrimitiveProperties($entryObject, $nonRelProp)
797
    {
798
        $propertyContent = new ODataPropertyContent();
799
        $cereal = $this->getModelSerialiser()->bulkSerialise($entryObject);
800
        foreach ($cereal as $corn => $flake) {
801
            if (!array_key_exists($corn, $nonRelProp)) {
802
                continue;
803
            }
804
            $corn = strval($corn);
805
            $rType = $nonRelProp[$corn]->getResourceType()->getInstanceType();
806
            $subProp = new ODataProperty();
807
            $subProp->name = $corn;
808
            $subProp->value = isset($flake) ? $this->primitiveToString($rType, $flake) : null;
809
            $subProp->typeName = $nonRelProp[$corn]->getResourceType()->getFullName();
810
            $propertyContent->properties[] = $subProp;
811
        }
812
        return $propertyContent;
813
    }
814
815
    /**
816
     * @param $entryObject
817
     * @param $prop
818
     * @param $nuLink
819
     * @param $propKind
820
     * @param $propName
821
     */
822
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
823
    {
824
        $nextName = $prop->getResourceType()->getName();
825
        $nuLink->isExpanded = true;
826
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
827
        $nuLink->isCollection = $isCollection;
828
        $value = $entryObject->results->$propName;
829
830
        $result = new QueryResult();
831
        $result->results = $value;
832
        $resultCount = $isCollection ? count($value) : 1;
833
        $newStackLine = ['type' => $nextName, 'prop' => $propName, 'count' => $resultCount];
834
        array_push($this->lightStack, $newStackLine);
835
836
        if (!$isCollection) {
837
            $expandedResult = $this->writeTopLevelElement($result);
838
        } else {
839
            $expandedResult = $this->writeTopLevelElements($result);
840
        }
841
        $nuLink->expandedResult = $expandedResult;
842
        if (!isset($nuLink->expandedResult)) {
843
            $nuLink->isCollection = null;
844
            $nuLink->isExpanded = null;
845
        } else {
846
            if (isset($nuLink->expandedResult->selfLink)) {
847
                $nuLink->expandedResult->selfLink->title = $propName;
848
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
849
                $nuLink->expandedResult->title = $propName;
850
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
851
            }
852
        }
853
    }
854
855
    /**
856
     * Gets the data service instance.
857
     *
858
     * @return void
859
     */
860
    public function setService(IService $service)
861
    {
862
        $this->service = $service;
863
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
864
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
865
    }
866
867
    /**
868
     * @param ResourceType $resourceType
869
     * @param $result
870
     * @return ODataBagContent|null
871
     */
872
    protected function writeBagValue(ResourceType &$resourceType, $result)
873
    {
874
        assert(null == $result || is_array($result), 'Bag parameter must be null or array');
875
        $typeKind = $resourceType->getResourceTypeKind();
876
        assert(
877
            ResourceTypeKind::PRIMITIVE() == $typeKind || ResourceTypeKind::COMPLEX() == $typeKind,
878
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
879
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
880
        );
881
        if (null == $result) {
882
            return null;
883
        }
884
        $bag = new ODataBagContent();
885
        foreach ($result as $value) {
886
            if (isset($value)) {
887
                if (ResourceTypeKind::PRIMITIVE() == $typeKind) {
888
                    $instance = $resourceType->getInstanceType();
889
                    assert($instance instanceof IType, get_class($instance));
890
                    $bag->propertyContents[] = $this->primitiveToString($instance, $value);
891
                } elseif (ResourceTypeKind::COMPLEX() == $typeKind) {
892
                    $bag->propertyContents[] = $this->writeComplexValue($resourceType, $value);
893
                }
894
            }
895
        }
896
        return $bag;
897
    }
898
899
    /**
900
     * @param ResourceType  $resourceType
901
     * @param object        $result
902
     * @param string|null   $propertyName
903
     * @return ODataPropertyContent
904
     */
905
    protected function writeComplexValue(ResourceType &$resourceType, &$result, $propertyName = null)
906
    {
907
        assert(is_object($result), 'Supplied $customObject must be an object');
908
909
        $count = count($this->complexTypeInstanceCollection);
910
        for ($i = 0; $i < $count; ++$i) {
911
            if ($this->complexTypeInstanceCollection[$i] === $result) {
912
                throw new InvalidOperationException(
913
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
914
                );
915
            }
916
        }
917
918
        $this->complexTypeInstanceCollection[$count] = &$result;
919
920
        $internalContent = new ODataPropertyContent();
921
        $resourceProperties = $resourceType->getAllProperties();
922
        // first up, handle primitive properties
923
        foreach ($resourceProperties as $prop) {
924
            $resourceKind = $prop->getKind();
925
            $propName = $prop->getName();
926
            $internalProperty = new ODataProperty();
927
            $internalProperty->name = $propName;
928
            if (static::isMatchPrimitive($resourceKind)) {
929
                $iType = $prop->getInstanceType();
930
                assert($iType instanceof IType, get_class($iType));
931
                $internalProperty->typeName = $iType->getFullTypeName();
932
933
                $rType = $prop->getResourceType()->getInstanceType();
934
                assert($rType instanceof IType, get_class($rType));
935
                $internalProperty->value = $this->primitiveToString($rType, $result->$propName);
936
937
                $internalContent->properties[] = $internalProperty;
938
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
939
                $rType = $prop->getResourceType();
940
                $internalProperty->typeName = $rType->getFullName();
941
                $internalProperty->value = $this->writeComplexValue($rType, $result->$propName, $propName);
942
943
                $internalContent->properties[] = $internalProperty;
944
            }
945
        }
946
947
        unset($this->complexTypeInstanceCollection[$count]);
948
        return $internalContent;
949
    }
950
951
    public static function isMatchPrimitive($resourceKind)
952
    {
953
        if (16 > $resourceKind) {
954
            return false;
955
        }
956
        if (28 < $resourceKind) {
957
            return false;
958
        }
959
        return 0 == ($resourceKind % 4);
960
    }
961
962
    /*
963
     * @return IMetadataProvider
964
     */
965
    protected function getMetadata()
966
    {
967
        if (null == $this->metaProvider) {
968
            $this->metaProvider = App::make('metadata');
969
        }
970
        return $this->metaProvider;
971
    }
972
973
    /**
974
     * @return array
975
     */
976
    protected function getLightStack()
977
    {
978
        return $this->lightStack;
979
    }
980
981
    /**
982
     * @return ModelSerialiser
983
     */
984
    public function getModelSerialiser()
985
    {
986
        return $this->modelSerialiser;
987
    }
988
}
989