Test Failed
Pull Request — master (#95)
by Alex
03:06
created

IronicSerialiser::writeTopLevelElements()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 52
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 52
rs 7.2396
cc 7
eloc 38
nc 24
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Serialisers;
4
5
use Carbon\Carbon;
6
use Illuminate\Support\Facades\App;
7
use POData\Common\InvalidOperationException;
8
use POData\Common\Messages;
9
use POData\Common\ODataConstants;
10
use POData\Common\ODataException;
11
use POData\IService;
12
use POData\ObjectModel\IObjectSerialiser;
13
use POData\ObjectModel\ODataBagContent;
14
use POData\ObjectModel\ODataEntry;
15
use POData\ObjectModel\ODataFeed;
16
use POData\ObjectModel\ODataLink;
17
use POData\ObjectModel\ODataMediaLink;
18
use POData\ObjectModel\ODataNavigationPropertyInfo;
19
use POData\ObjectModel\ODataProperty;
20
use POData\ObjectModel\ODataPropertyContent;
21
use POData\ObjectModel\ODataTitle;
22
use POData\ObjectModel\ODataURL;
23
use POData\ObjectModel\ODataURLCollection;
24
use POData\Providers\Metadata\IMetadataProvider;
25
use POData\Providers\Metadata\ResourceEntityType;
26
use POData\Providers\Metadata\ResourceProperty;
27
use POData\Providers\Metadata\ResourcePropertyKind;
28
use POData\Providers\Metadata\ResourceSet;
29
use POData\Providers\Metadata\ResourceSetWrapper;
30
use POData\Providers\Metadata\ResourceType;
31
use POData\Providers\Metadata\ResourceTypeKind;
32
use POData\Providers\Metadata\Type\Binary;
33
use POData\Providers\Metadata\Type\Boolean;
34
use POData\Providers\Metadata\Type\DateTime;
35
use POData\Providers\Metadata\Type\IType;
36
use POData\Providers\Metadata\Type\StringType;
37
use POData\Providers\Query\QueryResult;
38
use POData\Providers\Query\QueryType;
39
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
40
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode;
41
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\RootProjectionNode;
42
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
43
use POData\UriProcessor\RequestDescription;
44
use POData\UriProcessor\SegmentStack;
45
46
class IronicSerialiser implements IObjectSerialiser
47
{
48
    const PK = 'PrimaryKey';
49
50
    /**
51
     * The service implementation.
52
     *
53
     * @var IService
54
     */
55
    protected $service;
56
57
    /**
58
     * Request description instance describes OData request the
59
     * the client has submitted and result of the request.
60
     *
61
     * @var RequestDescription
62
     */
63
    protected $request;
64
65
    /**
66
     * Collection of complex type instances used for cycle detection.
67
     *
68
     * @var array
69
     */
70
    protected $complexTypeInstanceCollection;
71
72
    /**
73
     * Absolute service Uri.
74
     *
75
     * @var string
76
     */
77
    protected $absoluteServiceUri;
78
79
    /**
80
     * Absolute service Uri with slash.
81
     *
82
     * @var string
83
     */
84
    protected $absoluteServiceUriWithSlash;
85
86
    /**
87
     * Holds reference to segment stack being processed.
88
     *
89
     * @var SegmentStack
90
     */
91
    protected $stack;
92
93
    /**
94
     * Lightweight stack tracking for recursive descent fill
95
     */
96
    protected $lightStack = [];
97
98
    /**
99
     * @var ModelSerialiser
100
     */
101
    private $modelSerialiser;
102
103
104
105
    /**
106
     * @var IMetadataProvider
107
     */
108
    private $metaProvider;
109
110
    /*
111
     * Update time to insert into ODataEntry/ODataFeed fields
112
     * @var Carbon;
113
     */
114
    private $updated;
115
116
    /**
117
     * @param IService                  $service    Reference to the data service instance
118
     * @param RequestDescription|null   $request    Type instance describing the client submitted request
119
     */
120
    public function __construct(IService $service, RequestDescription $request = null)
121
    {
122
        $this->service = $service;
123
        $this->request = $request;
124
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
125
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
126
        $this->stack = new SegmentStack($request);
127
        $this->complexTypeInstanceCollection = [];
128
        $this->modelSerialiser = new ModelSerialiser();
129
        $this->updated = Carbon::now();
130
    }
131
132
    /**
133
     * Write a top level entry resource.
134
     *
135
     * @param QueryResult $entryObject Reference to the entry object to be written
136
     *
137
     * @return ODataEntry|null
138
     */
139
    public function writeTopLevelElement(QueryResult $entryObject)
140
    {
141
        if (!isset($entryObject->results)) {
142
            array_pop($this->lightStack);
143
            return null;
144
        }
145
146
        $this->loadStackIfEmpty();
147
148
        $stackCount = count($this->lightStack);
149
        $topOfStack = $this->lightStack[$stackCount-1];
150
        $payloadClass = get_class($entryObject->results);
151
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack['type']);
152
        // need gubbinz to unpack an abstract resource type
153
        if ($resourceType->isAbstract()) {
154
            $derived = $this->getMetadata()->getDerivedTypes($resourceType);
155
            assert(0 < count($derived));
156
            foreach ($derived as $rawType) {
157
                if (!$rawType->isAbstract()) {
158
                    $name = $rawType->getInstanceType()->getName();
159
                    if ($payloadClass == $name) {
160
                        $resourceType = $rawType;
161
                        break;
162
                    }
163
                }
164
            }
165
            // despite all set up, checking, etc, if we haven't picked a concrete resource type,
166
            // wheels have fallen off, so blow up
167
            assert(!$resourceType->isAbstract(), 'Concrete resource type not selected for payload '.$payloadClass);
168
        }
169
170
        // make sure we're barking up right tree
171
        assert($resourceType instanceof ResourceEntityType, get_class($resourceType));
172
        $targClass = $resourceType->getInstanceType()->getName();
173
        assert(
174
            $entryObject->results instanceof $targClass,
175
            'Object being serialised not instance of expected class, '.$targClass. ', is actually '.$payloadClass
176
        );
177
178
        $rawProp = $resourceType->getAllProperties();
179
        $relProp = [];
180
        $nonRelProp = [];
181
        foreach ($rawProp as $prop) {
182
            if ($prop->getResourceType() instanceof ResourceEntityType) {
183
                $relProp[] = $prop;
184
            } else {
185
                $nonRelProp[$prop->getName()] = $prop;
186
            }
187
        }
188
189
        $resourceSet = $resourceType->getCustomState();
190
        assert($resourceSet instanceof ResourceSet);
191
        $title = $resourceType->getName();
192
        $type = $resourceType->getFullName();
193
194
        $relativeUri = $this->getEntryInstanceKey(
195
            $entryObject->results,
196
            $resourceType,
197
            $resourceSet->getName()
198
        );
199
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
200
201
        list($mediaLink, $mediaLinks) = $this->writeMediaData(
202
            $entryObject->results,
203
            $type,
204
            $relativeUri,
205
            $resourceType
206
        );
207
208
        $propertyContent = $this->writePrimitiveProperties($entryObject->results, $nonRelProp);
209
210
        $links = [];
211
        foreach ($relProp as $prop) {
212
            $nuLink = new ODataLink();
213
            $propKind = $prop->getKind();
214
215
            assert(
216
                ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
217
                || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind,
218
                '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
219
                .' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE'
220
            );
221
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
222
            $propType = 'application/atom+xml;type='.$propTail;
223
            $propName = $prop->getName();
224
            $nuLink->title = $propName;
225
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
226
            $nuLink->url = $relativeUri . '/' . $propName;
227
            $nuLink->type = $propType;
228
229
            $navProp = new ODataNavigationPropertyInfo($prop, $this->shouldExpandSegment($propName));
230
            if ($navProp->expanded) {
231
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
232
            }
233
234
            $links[] = $nuLink;
235
        }
236
237
        $odata = new ODataEntry();
238
        $odata->resourceSetName = $resourceSet->getName();
239
        $odata->id = $absoluteUri;
240
        $odata->title = new ODataTitle($title);
1 ignored issue
show
Documentation Bug introduced by
It seems like new \POData\ObjectModel\ODataTitle($title) of type object<POData\ObjectModel\ODataTitle> is incompatible with the declared type string of property $title.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
241
        $odata->type = $type;
242
        $odata->propertyContent = $propertyContent;
243
        $odata->isMediaLinkEntry = $resourceType->isMediaLinkEntry();
244
        $odata->editLink = $relativeUri;
245
        $odata->mediaLink = $mediaLink;
246
        $odata->mediaLinks = $mediaLinks;
247
        $odata->links = $links;
248
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
1 ignored issue
show
Bug introduced by
The property updated does not seem to exist in POData\ObjectModel\ODataEntry.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
249
250
        $newCount = count($this->lightStack);
251
        assert(
252
            $newCount == $stackCount,
253
            'Should have ' . $stackCount . ' elements in stack, have ' . $newCount . ' elements'
254
        );
255
        $this->lightStack[$newCount-1]['count']--;
256
        if (0 == $this->lightStack[$newCount-1]['count']) {
257
            array_pop($this->lightStack);
258
        }
259
        return $odata;
260
    }
261
262
    /**
263
     * Write top level feed element.
264
     *
265
     * @param QueryResult &$entryObjects Array of entry resources to be written
266
     *
267
     * @return ODataFeed
268
     */
269
    public function writeTopLevelElements(QueryResult &$entryObjects)
270
    {
271
        assert(is_array($entryObjects->results), '!is_array($entryObjects->results)');
272
273
        $this->loadStackIfEmpty();
274
        $setName = $this->getRequest()->getTargetResourceSetWrapper()->getName();
275
276
        $title = $this->getRequest()->getContainerName();
277
        $relativeUri = $this->getRequest()->getIdentifier();
278
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
279
280
        $selfLink = new ODataLink();
281
        $selfLink->name = 'self';
282
        $selfLink->title = $relativeUri;
283
        $selfLink->url = $relativeUri;
284
285
        $odata = new ODataFeed();
286
        $odata->title = new ODataTitle($title);
1 ignored issue
show
Documentation Bug introduced by
It seems like new \POData\ObjectModel\ODataTitle($title) of type object<POData\ObjectModel\ODataTitle> is incompatible with the declared type string of property $title.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
287
        $odata->id = $absoluteUri;
288
        $odata->selfLink = $selfLink;
289
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
1 ignored issue
show
Bug introduced by
The property updated does not seem to exist in POData\ObjectModel\ODataFeed.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
290
291
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
292
            $odata->rowCount = $this->getRequest()->getCountValue();
293
        }
294
        foreach ($entryObjects->results as $entry) {
295
            if (!$entry instanceof QueryResult) {
296
                $query = new QueryResult();
297
                $query->results = $entry;
298
            } else {
299
                $query = $entry;
300
            }
301
            $odata->entries[] = $this->writeTopLevelElement($query);
302
        }
303
304
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
305
        $requestTop = $this->getRequest()->getTopOptionCount();
306
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
307
        $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...
308
309
        if (true === $entryObjects->hasMore && $requestTop > $pageSize) {
310
            $stackSegment = $setName;
311
            $lastObject = end($entryObjects->results);
312
            $segment = $this->getNextLinkUri($lastObject, $absoluteUri);
313
            $nextLink = new ODataLink();
314
            $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
315
            $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
316
            $odata->nextPageLink = $nextLink;
317
        }
318
319
        return $odata;
320
    }
321
322
    /**
323
     * Write top level url element.
324
     *
325
     * @param QueryResult $entryObject The entry resource whose url to be written
326
     *
327
     * @return ODataURL
328
     */
329
    public function writeUrlElement(QueryResult $entryObject)
330
    {
331
        $url = new ODataURL();
332
        if (!is_null($entryObject->results)) {
333
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
334
            $relativeUri = $this->getEntryInstanceKey(
335
                $entryObject->results,
336
                $currentResourceType,
337
                $this->getCurrentResourceSetWrapper()->getName()
338
            );
339
340
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
341
        }
342
343
        return $url;
344
    }
345
346
    /**
347
     * Write top level url collection.
348
     *
349
     * @param QueryResult $entryObjects Array of entry resources whose url to be written
350
     *
351
     * @return ODataURLCollection
352
     */
353
    public function writeUrlElements(QueryResult $entryObjects)
354
    {
355
        $urls = new ODataURLCollection();
356
        if (!empty($entryObjects->results)) {
357
            $i = 0;
358
            foreach ($entryObjects->results as $entryObject) {
359
                $urls->urls[$i] = $this->writeUrlElement($entryObject);
360
                ++$i;
361
            }
362
363
            if ($i > 0 && true === $entryObjects->hasMore) {
364
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
365
                $lastObject = end($entryObjects->results);
366
                $segment = $this->getNextLinkUri($lastObject, $this->getRequest()->getRequestUrl()->getUrlAsString());
367
                $nextLink = new ODataLink();
368
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
369
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
370
                $urls->nextPageLink = $nextLink;
371
            }
372
        }
373
374
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
375
            $urls->count = $this->getRequest()->getCountValue();
376
        }
377
378
        return $urls;
379
    }
380
381
    /**
382
     * Write top level complex resource.
383
     *
384
     * @param QueryResult &$complexValue The complex object to be written
385
     * @param string $propertyName The name of the complex property
386
     * @param ResourceType &$resourceType Describes the type of complex object
387
     *
388
     * @return ODataPropertyContent
389
     */
390
    public function writeTopLevelComplexObject(QueryResult &$complexValue, $propertyName, ResourceType &$resourceType)
391
    {
392
        $result = $complexValue->results;
393
394
        $propertyContent = new ODataPropertyContent();
395
        $odataProperty = new ODataProperty();
396
        $odataProperty->name = $propertyName;
397
        $odataProperty->typeName = $resourceType->getFullName();
398
        if (null != $result) {
399
            $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...
400
            $odataProperty->value = $internalContent;
401
        }
402
403
        $propertyContent->properties[] = $odataProperty;
404
405
        return $propertyContent;
406
    }
407
408
    /**
409
     * Write top level bag resource.
410
     *
411
     * @param QueryResult &$BagValue The bag object to be
412
     *                                    written
413
     * @param string $propertyName The name of the
414
     *                                    bag property
415
     * @param ResourceType &$resourceType Describes the type of
416
     *                                    bag object
417
     * @return ODataPropertyContent
418
     */
419
    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...
420
    {
421
        $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...
422
423
        $propertyContent = new ODataPropertyContent();
424
        $odataProperty = new ODataProperty();
425
        $odataProperty->name = $propertyName;
426
        $odataProperty->typeName = 'Collection('.$resourceType->getFullName().')';
427
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
428
429
        $propertyContent->properties[] = $odataProperty;
430
        return $propertyContent;
431
    }
432
433
    /**
434
     * Write top level primitive value.
435
     *
436
     * @param QueryResult &$primitiveValue              The primitive value to be
437
     *                                            written
438
     * @param ResourceProperty &$resourceProperty Resource property describing the
439
     *                                            primitive property to be written
440
     * @return ODataPropertyContent
441
     */
442
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
443
    {
444
        assert(null != $resourceProperty, 'Resource property must not be null');
445
        $propertyContent = new ODataPropertyContent();
446
447
        $odataProperty = new ODataProperty();
448
        $odataProperty->name = $resourceProperty->getName();
449
        $iType = $resourceProperty->getInstanceType();
450
        assert($iType instanceof IType, get_class($iType));
451
        $odataProperty->typeName = $iType->getFullTypeName();
452
        if (null == $primitiveValue->results) {
453
            $odataProperty->value = null;
454
        } else {
455
            $rType = $resourceProperty->getResourceType()->getInstanceType();
456
            assert($rType instanceof IType, get_class($rType));
457
            $odataProperty->value = $this->primitiveToString($rType, $primitiveValue->results);
458
        }
459
460
        $propertyContent->properties[] = $odataProperty;
461
462
        return $propertyContent;
463
    }
464
465
    /**
466
     * Gets reference to the request submitted by client.
467
     *
468
     * @return RequestDescription
469
     */
470
    public function getRequest()
471
    {
472
        assert(null != $this->request, 'Request not yet set');
473
474
        return $this->request;
475
    }
476
477
    /**
478
     * Sets reference to the request submitted by client.
479
     *
480
     * @param RequestDescription $request
481
     */
482
    public function setRequest(RequestDescription $request)
483
    {
484
        $this->request = $request;
485
        $this->stack->setRequest($request);
486
    }
487
488
    /**
489
     * Gets the data service instance.
490
     *
491
     * @return IService
492
     */
493
    public function getService()
494
    {
495
        return $this->service;
496
    }
497
498
    /**
499
     * Gets the segment stack instance.
500
     *
501
     * @return SegmentStack
502
     */
503
    public function getStack()
504
    {
505
        return $this->stack;
506
    }
507
508
    /**
509
     * Get update timestamp
510
     *
511
     * @return Carbon
512
     */
513
    public function getUpdated()
514
    {
515
        return $this->updated;
516
    }
517
518
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
519
    {
520
        $typeName = $resourceType->getName();
521
        $keyProperties = $resourceType->getKeyProperties();
522
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
523
        $keyString = $containerName . '(';
524
        $comma = null;
525
        foreach ($keyProperties as $keyName => $resourceProperty) {
526
            $keyType = $resourceProperty->getInstanceType();
527
            assert($keyType instanceof IType, '$keyType not instanceof IType');
528
            $keyName = $resourceProperty->getName();
529
            $keyValue = (self::PK == $keyName) ? $entityInstance->getKey() : $entityInstance->$keyName;
530
            if (!isset($keyValue)) {
531
                throw ODataException::createInternalServerError(
532
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
533
                );
534
            }
535
536
            $keyValue = $keyType->convertToOData($keyValue);
537
            $keyString .= $comma . $keyName . '=' . $keyValue;
538
            $comma = ',';
539
        }
540
541
        $keyString .= ')';
542
543
        return $keyString;
544
    }
545
546
    /**
547
     * @param $entryObject
548
     * @param $type
549
     * @param $relativeUri
550
     * @param $resourceType
551
     * @return array<ODataMediaLink|null|array>
552
     */
553
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
554
    {
555
        $context = $this->getService()->getOperationContext();
556
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
557
        assert(null != $streamProviderWrapper, 'Retrieved stream provider must not be null');
558
559
        $mediaLink = null;
560
        if ($resourceType->isMediaLinkEntry()) {
561
            $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...
562
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag);
563
        }
564
        $mediaLinks = [];
565
        if ($resourceType->hasNamedStream()) {
566
            $namedStreams = $resourceType->getAllNamedStreams();
567
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
568
                $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...
569
                    $entryObject,
570
                    $resourceStreamInfo,
571
                    $context,
572
                    $relativeUri
573
                );
574
                $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...
575
                    $entryObject,
576
                    $resourceStreamInfo,
577
                    $context
578
                );
579
                $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...
580
                    $entryObject,
581
                    $resourceStreamInfo,
582
                    $context
583
                );
584
585
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
586
                $mediaLinks[] = $nuLink;
587
            }
588
        }
589
        return [$mediaLink, $mediaLinks];
590
    }
591
592
    /**
593
     * Gets collection of projection nodes under the current node.
594
     *
595
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes describing projections for the current
596
     *                                                        segment, If this method returns null it means no
597
     *                                                        projections are to be applied and the entire resource for
598
     *                                                        the current segment should be serialized, If it returns
599
     *                                                        non-null only the properties described by the returned
600
     *                                                        projection segments should be serialized
601
     */
602
    protected function getProjectionNodes()
603
    {
604
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
605
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
606
            return null;
607
        }
608
609
        return $expandedProjectionNode->getChildNodes();
610
    }
611
612
    /**
613
     * Find a 'ExpandedProjectionNode' instance in the projection tree
614
     * which describes the current segment.
615
     *
616
     * @return null|RootProjectionNode|ExpandedProjectionNode
617
     */
618
    protected function getCurrentExpandedProjectionNode()
619
    {
620
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
621
        if (is_null($expandedProjectionNode)) {
622
            return null;
623
        } else {
624
            $segmentNames = $this->getLightStack();
625
            $depth = count($segmentNames);
626
            // $depth == 1 means serialization of root entry
627
            //(the resource identified by resource path) is going on,
628
            //so control won't get into the below for loop.
629
            //we will directly return the root node,
630
            //which is 'ExpandedProjectionNode'
631
            // for resource identified by resource path.
632
            if (0 != $depth) {
633
                for ($i = 1; $i < $depth; ++$i) {
634
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]['prop']);
635
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
636
                    assert(
637
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
638
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
639
                    );
640
                }
641
            }
642
        }
643
644
        return $expandedProjectionNode;
645
    }
646
647
    /**
648
     * Check whether to expand a navigation property or not.
649
     *
650
     * @param string $navigationPropertyName Name of naviagtion property in question
651
     *
652
     * @return bool True if the given navigation should be expanded, otherwise false
653
     */
654
    protected function shouldExpandSegment($navigationPropertyName)
655
    {
656
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
657
        if (is_null($expandedProjectionNode)) {
658
            return false;
659
        }
660
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
661
662
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
663
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
664
    }
665
666
    /**
667
     * Wheter next link is needed for the current resource set (feed)
668
     * being serialized.
669
     *
670
     * @param int $resultSetCount Number of entries in the current
671
     *                            resource set
672
     *
673
     * @return bool true if the feed must have a next page link
674
     */
675
    protected function needNextPageLink($resultSetCount)
676
    {
677
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
678
        $recursionLevel = count($this->getStack()->getSegmentNames());
679
        $pageSize = $currentResourceSet->getResourceSetPageSize();
680
681
        if (1 == $recursionLevel) {
682
            //presence of $top option affect next link for root container
683
            $topValueCount = $this->getRequest()->getTopOptionCount();
684
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
685
                return false;
686
            }
687
        }
688
        return $resultSetCount == $pageSize;
689
    }
690
691
    /**
692
     * Resource set wrapper for the resource being serialized.
693
     *
694
     * @return ResourceSetWrapper
695
     */
696
    protected function getCurrentResourceSetWrapper()
697
    {
698
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
699
        $count = count($segmentWrappers);
700
701
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
702
    }
703
704
    /**
705
     * Get next page link from the given entity instance.
706
     *
707
     * @param mixed  &$lastObject Last object serialized to be
708
     *                            used for generating $skiptoken
709
     * @param string $absoluteUri Absolute response URI
710
     *
711
     * @return string for the link for next page
712
     */
713
    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...
714
    {
715
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
716
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
717
        assert(null != $internalOrderByInfo);
718
        assert(is_object($internalOrderByInfo));
719
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
720
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
721
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
722
723
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
724
        assert(!is_null($skipToken), '!is_null($skipToken)');
725
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
726
        $skipToken = '?'.$queryParameterString.$token.$skipToken;
727
728
        return $skipToken;
729
    }
730
731
    /**
732
     * Builds the string corresponding to query parameters for top level results
733
     * (result set identified by the resource path) to be put in next page link.
734
     *
735
     * @return string|null string representing the query parameters in the URI
736
     *                     query parameter format, NULL if there
737
     *                     is no query parameters
738
     *                     required for the next link of top level result set
739
     */
740
    protected function getNextPageLinkQueryParametersForRootResourceSet()
741
    {
742
        $queryParameterString = null;
743
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
744
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
745
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
746
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
747
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
748
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
749
            if (!is_null($value)) {
750
                if (!is_null($queryParameterString)) {
751
                    $queryParameterString = $queryParameterString . '&';
752
                }
753
754
                $queryParameterString .= $queryOption . '=' . $value;
755
            }
756
        }
757
758
        $topCountValue = $this->getRequest()->getTopOptionCount();
759
        if (!is_null($topCountValue)) {
760
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
761
            if (0 < $remainingCount) {
762
                if (!is_null($queryParameterString)) {
763
                    $queryParameterString .= '&';
764
                }
765
766
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
767
            }
768
        }
769
770
        if (!is_null($queryParameterString)) {
771
            $queryParameterString .= '&';
772
        }
773
774
        return $queryParameterString;
775
    }
776
777
    private function loadStackIfEmpty()
778
    {
779
        if (0 == count($this->lightStack)) {
780
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
781
            array_push($this->lightStack, ['type' => $typeName, 'property' => $typeName, 'count' => 1]);
782
        }
783
    }
784
785
    /**
786
     * Convert the given primitive value to string.
787
     * Note: This method will not handle null primitive value.
788
     *
789
     * @param IType &$primitiveResourceType        Type of the primitive property
790
     *                                             whose value need to be converted
791
     * @param mixed        $primitiveValue         Primitive value to convert
792
     *
793
     * @return string
794
     */
795
    private function primitiveToString(IType &$type, $primitiveValue)
796
    {
797
        if ($type instanceof Boolean) {
798
            $stringValue = (true === $primitiveValue) ? 'true' : 'false';
799
        } elseif ($type instanceof Binary) {
800
            $stringValue = base64_encode($primitiveValue);
801
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
802
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
803
        } elseif ($type instanceof StringType) {
804
            $stringValue = utf8_encode($primitiveValue);
805
        } else {
806
            $stringValue = strval($primitiveValue);
807
        }
808
809
        return $stringValue;
810
    }
811
812
    /**
813
     * @param $entryObject
814
     * @param $nonRelProp
815
     * @return ODataPropertyContent
816
     */
817
    private function writePrimitiveProperties($entryObject, $nonRelProp)
818
    {
819
        $propertyContent = new ODataPropertyContent();
820
        $cereal = $this->getModelSerialiser()->bulkSerialise($entryObject);
821
        foreach ($cereal as $corn => $flake) {
822
            if (!array_key_exists($corn, $nonRelProp)) {
823
                continue;
824
            }
825
            $corn = strval($corn);
826
            $rType = $nonRelProp[$corn]->getResourceType()->getInstanceType();
827
            $subProp = new ODataProperty();
828
            $subProp->name = $corn;
829
            $subProp->value = isset($flake) ? $this->primitiveToString($rType, $flake) : null;
830
            $subProp->typeName = $nonRelProp[$corn]->getResourceType()->getFullName();
831
            $propertyContent->properties[] = $subProp;
832
        }
833
        return $propertyContent;
834
    }
835
836
    /**
837
     * @param $entryObject
838
     * @param $prop
839
     * @param $nuLink
840
     * @param $propKind
841
     * @param $propName
842
     */
843
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
844
    {
845
        $nextName = $prop->getResourceType()->getName();
846
        $nuLink->isExpanded = true;
847
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
848
        $nuLink->isCollection = $isCollection;
849
        $value = $entryObject->results->$propName;
850
851
        $result = new QueryResult();
852
        $result->results = $value;
853
        $resultCount = $isCollection ? count($value) : 1;
854
        $newStackLine = ['type' => $nextName, 'prop' => $propName, 'count' => $resultCount];
855
        array_push($this->lightStack, $newStackLine);
856
857
        if (!$isCollection) {
858
            $expandedResult = $this->writeTopLevelElement($result);
859
        } else {
860
            $expandedResult = $this->writeTopLevelElements($result);
861
        }
862
        $nuLink->expandedResult = $expandedResult;
863
        if (!isset($nuLink->expandedResult)) {
864
            $nuLink->isCollection = null;
865
            $nuLink->isExpanded = null;
866
        } else {
867
            if (isset($nuLink->expandedResult->selfLink)) {
868
                $nuLink->expandedResult->selfLink->title = $propName;
869
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
870
                $nuLink->expandedResult->title = new ODataTitle($propName);
871
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
872
            }
873
        }
874
    }
875
876
    /**
877
     * Gets the data service instance.
878
     *
879
     * @return void
880
     */
881
    public function setService(IService $service)
882
    {
883
        $this->service = $service;
884
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
885
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
886
    }
887
888
    /**
889
     * @param ResourceType $resourceType
890
     * @param $result
891
     * @return ODataBagContent|null
892
     */
893
    protected function writeBagValue(ResourceType &$resourceType, $result)
894
    {
895
        assert(null == $result || is_array($result), 'Bag parameter must be null or array');
896
        $typeKind = $resourceType->getResourceTypeKind();
897
        assert(
898
            ResourceTypeKind::PRIMITIVE() == $typeKind || ResourceTypeKind::COMPLEX() == $typeKind,
899
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
900
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
901
        );
902
        if (null == $result) {
903
            return null;
904
        }
905
        $bag = new ODataBagContent();
906
        foreach ($result as $value) {
907
            if (isset($value)) {
908
                if (ResourceTypeKind::PRIMITIVE() == $typeKind) {
909
                    $instance = $resourceType->getInstanceType();
910
                    assert($instance instanceof IType, get_class($instance));
911
                    $bag->propertyContents[] = $this->primitiveToString($instance, $value);
912
                } elseif (ResourceTypeKind::COMPLEX() == $typeKind) {
913
                    $bag->propertyContents[] = $this->writeComplexValue($resourceType, $value);
914
                }
915
            }
916
        }
917
        return $bag;
918
    }
919
920
    /**
921
     * @param ResourceType  $resourceType
922
     * @param object        $result
923
     * @param string|null   $propertyName
924
     * @return ODataPropertyContent
925
     */
926
    protected function writeComplexValue(ResourceType &$resourceType, &$result, $propertyName = null)
927
    {
928
        assert(is_object($result), 'Supplied $customObject must be an object');
929
930
        $count = count($this->complexTypeInstanceCollection);
931
        for ($i = 0; $i < $count; ++$i) {
932
            if ($this->complexTypeInstanceCollection[$i] === $result) {
933
                throw new InvalidOperationException(
934
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
935
                );
936
            }
937
        }
938
939
        $this->complexTypeInstanceCollection[$count] = &$result;
940
941
        $internalContent = new ODataPropertyContent();
942
        $resourceProperties = $resourceType->getAllProperties();
943
        // first up, handle primitive properties
944
        foreach ($resourceProperties as $prop) {
945
            $resourceKind = $prop->getKind();
946
            $propName = $prop->getName();
947
            $internalProperty = new ODataProperty();
948
            $internalProperty->name = $propName;
949
            if (static::isMatchPrimitive($resourceKind)) {
950
                $iType = $prop->getInstanceType();
951
                assert($iType instanceof IType, get_class($iType));
952
                $internalProperty->typeName = $iType->getFullTypeName();
953
954
                $rType = $prop->getResourceType()->getInstanceType();
955
                assert($rType instanceof IType, get_class($rType));
956
                $internalProperty->value = $this->primitiveToString($rType, $result->$propName);
957
958
                $internalContent->properties[] = $internalProperty;
959
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
960
                $rType = $prop->getResourceType();
961
                $internalProperty->typeName = $rType->getFullName();
962
                $internalProperty->value = $this->writeComplexValue($rType, $result->$propName, $propName);
963
964
                $internalContent->properties[] = $internalProperty;
965
            }
966
        }
967
968
        unset($this->complexTypeInstanceCollection[$count]);
969
        return $internalContent;
970
    }
971
972
    public static function isMatchPrimitive($resourceKind)
973
    {
974
        if (16 > $resourceKind) {
975
            return false;
976
        }
977
        if (28 < $resourceKind) {
978
            return false;
979
        }
980
        return 0 == ($resourceKind % 4);
981
    }
982
983
    /*
984
     * @return IMetadataProvider
985
     */
986
    protected function getMetadata()
987
    {
988
        if (null == $this->metaProvider) {
989
            $this->metaProvider = App::make('metadata');
990
        }
991
        return $this->metaProvider;
992
    }
993
994
    /**
995
     * @return array
996
     */
997
    protected function getLightStack()
998
    {
999
        return $this->lightStack;
1000
    }
1001
1002
    /**
1003
     * @return ModelSerialiser
1004
     */
1005
    public function getModelSerialiser()
1006
    {
1007
        return $this->modelSerialiser;
1008
    }
1009
}
1010