Test Failed
Push — master ( 992006...775845 )
by Alex
04:30
created

IronicSerialiser::writeTopLevelElement()   F

Complexity

Conditions 14
Paths 301

Size

Total Lines 129
Code Lines 93

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 129
rs 3.7522
cc 14
eloc 93
nc 301
nop 1

How to fix   Long Method    Complexity   

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\ODataCategory;
15
use POData\ObjectModel\ODataEntry;
16
use POData\ObjectModel\ODataFeed;
17
use POData\ObjectModel\ODataLink;
18
use POData\ObjectModel\ODataMediaLink;
19
use POData\ObjectModel\ODataNavigationPropertyInfo;
20
use POData\ObjectModel\ODataProperty;
21
use POData\ObjectModel\ODataPropertyContent;
22
use POData\ObjectModel\ODataTitle;
23
use POData\ObjectModel\ODataURL;
24
use POData\ObjectModel\ODataURLCollection;
25
use POData\Providers\Metadata\IMetadataProvider;
26
use POData\Providers\Metadata\ResourceEntityType;
27
use POData\Providers\Metadata\ResourceProperty;
28
use POData\Providers\Metadata\ResourcePropertyKind;
29
use POData\Providers\Metadata\ResourceSet;
30
use POData\Providers\Metadata\ResourceSetWrapper;
31
use POData\Providers\Metadata\ResourceType;
32
use POData\Providers\Metadata\ResourceTypeKind;
33
use POData\Providers\Metadata\Type\Binary;
34
use POData\Providers\Metadata\Type\Boolean;
35
use POData\Providers\Metadata\Type\DateTime;
36
use POData\Providers\Metadata\Type\IType;
37
use POData\Providers\Metadata\Type\StringType;
38
use POData\Providers\Query\QueryResult;
39
use POData\Providers\Query\QueryType;
40
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
41
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode;
42
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\RootProjectionNode;
43
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
44
use POData\UriProcessor\RequestDescription;
45
use POData\UriProcessor\SegmentStack;
46
47
class IronicSerialiser implements IObjectSerialiser
48
{
49
    const PK = 'PrimaryKey';
50
51
    /**
52
     * The service implementation.
53
     *
54
     * @var IService
55
     */
56
    protected $service;
57
58
    /**
59
     * Request description instance describes OData request the
60
     * the client has submitted and result of the request.
61
     *
62
     * @var RequestDescription
63
     */
64
    protected $request;
65
66
    /**
67
     * Collection of complex type instances used for cycle detection.
68
     *
69
     * @var array
70
     */
71
    protected $complexTypeInstanceCollection;
72
73
    /**
74
     * Absolute service Uri.
75
     *
76
     * @var string
77
     */
78
    protected $absoluteServiceUri;
79
80
    /**
81
     * Absolute service Uri with slash.
82
     *
83
     * @var string
84
     */
85
    protected $absoluteServiceUriWithSlash;
86
87
    /**
88
     * Holds reference to segment stack being processed.
89
     *
90
     * @var SegmentStack
91
     */
92
    protected $stack;
93
94
    /**
95
     * Lightweight stack tracking for recursive descent fill
96
     */
97
    protected $lightStack = [];
98
99
    /**
100
     * @var ModelSerialiser
101
     */
102
    private $modelSerialiser;
103
104
105
106
    /**
107
     * @var IMetadataProvider
108
     */
109
    private $metaProvider;
110
111
    /*
112
     * Update time to insert into ODataEntry/ODataFeed fields
113
     * @var Carbon;
114
     */
115
    private $updated;
116
117
    /*
118
     * Has base URI already been written out during serialisation?
119
     * @var bool;
120
     */
121
    private $isBaseWritten = false;
122
123
    /**
124
     * @param IService                  $service    Reference to the data service instance
125
     * @param RequestDescription|null   $request    Type instance describing the client submitted request
126
     */
127
    public function __construct(IService $service, RequestDescription $request = null)
128
    {
129
        $this->service = $service;
130
        $this->request = $request;
131
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
132
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
133
        $this->stack = new SegmentStack($request);
134
        $this->complexTypeInstanceCollection = [];
135
        $this->modelSerialiser = new ModelSerialiser();
136
        $this->updated = Carbon::now();
137
    }
138
139
    /**
140
     * Write a top level entry resource.
141
     *
142
     * @param QueryResult $entryObject Reference to the entry object to be written
143
     *
144
     * @return ODataEntry|null
145
     */
146
    public function writeTopLevelElement(QueryResult $entryObject)
147
    {
148
        if (!isset($entryObject->results)) {
149
            array_pop($this->lightStack);
150
            return null;
151
        }
152
153
        $this->loadStackIfEmpty();
154
155
        $baseURI = $this->isBaseWritten ? null : $this->absoluteServiceUriWithSlash;
156
        $this->isBaseWritten = true;
157
158
        $stackCount = count($this->lightStack);
159
        $topOfStack = $this->lightStack[$stackCount-1];
160
        $payloadClass = get_class($entryObject->results);
161
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack['type']);
162
        // need gubbinz to unpack an abstract resource type
163
        if ($resourceType->isAbstract()) {
164
            $derived = $this->getMetadata()->getDerivedTypes($resourceType);
165
            assert(0 < count($derived));
166
            foreach ($derived as $rawType) {
167
                if (!$rawType->isAbstract()) {
168
                    $name = $rawType->getInstanceType()->getName();
169
                    if ($payloadClass == $name) {
170
                        $resourceType = $rawType;
171
                        break;
172
                    }
173
                }
174
            }
175
            // despite all set up, checking, etc, if we haven't picked a concrete resource type,
176
            // wheels have fallen off, so blow up
177
            assert(!$resourceType->isAbstract(), 'Concrete resource type not selected for payload '.$payloadClass);
178
        }
179
180
        // make sure we're barking up right tree
181
        assert($resourceType instanceof ResourceEntityType, get_class($resourceType));
182
        $targClass = $resourceType->getInstanceType()->getName();
183
        assert(
184
            $entryObject->results instanceof $targClass,
185
            'Object being serialised not instance of expected class, '.$targClass. ', is actually '.$payloadClass
186
        );
187
188
        $rawProp = $resourceType->getAllProperties();
189
        $relProp = [];
190
        $nonRelProp = [];
191
        foreach ($rawProp as $prop) {
192
            if ($prop->getResourceType() instanceof ResourceEntityType) {
193
                $relProp[] = $prop;
194
            } else {
195
                $nonRelProp[$prop->getName()] = $prop;
196
            }
197
        }
198
199
        $resourceSet = $resourceType->getCustomState();
200
        assert($resourceSet instanceof ResourceSet);
201
        $title = $resourceType->getName();
202
        $type = $resourceType->getFullName();
203
204
        $relativeUri = $this->getEntryInstanceKey(
205
            $entryObject->results,
206
            $resourceType,
207
            $resourceSet->getName()
208
        );
209
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
210
211
        list($mediaLink, $mediaLinks) = $this->writeMediaData(
212
            $entryObject->results,
213
            $type,
214
            $relativeUri,
215
            $resourceType
216
        );
217
218
        $propertyContent = $this->writePrimitiveProperties($entryObject->results, $nonRelProp);
219
220
        $links = [];
221
        foreach ($relProp as $prop) {
222
            $nuLink = new ODataLink();
223
            $propKind = $prop->getKind();
224
225
            assert(
226
                ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
227
                || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind,
228
                '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
229
                .' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE'
230
            );
231
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
232
            $propType = 'application/atom+xml;type='.$propTail;
233
            $propName = $prop->getName();
234
            $nuLink->title = $propName;
235
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
236
            $nuLink->url = $relativeUri . '/' . $propName;
237
            $nuLink->type = $propType;
238
239
            $navProp = new ODataNavigationPropertyInfo($prop, $this->shouldExpandSegment($propName));
240
            if ($navProp->expanded) {
241
                $this->expandNavigationProperty($entryObject, $prop, $nuLink, $propKind, $propName);
242
            }
243
244
            $links[] = $nuLink;
245
        }
246
247
        $odata = new ODataEntry();
248
        $odata->resourceSetName = $resourceSet->getName();
249
        $odata->id = $absoluteUri;
250
        $odata->title = new ODataTitle($title);
251
        $odata->type = new ODataCategory($type);
252
        $odata->propertyContent = $propertyContent;
253
        $odata->isMediaLinkEntry = $resourceType->isMediaLinkEntry();
254
        $odata->editLink = new ODataLink();
255
        $odata->editLink->url = $relativeUri;
256
        $odata->editLink->name = 'edit';
257
        $odata->editLink->title = $title;
258
        $odata->mediaLink = $mediaLink;
259
        $odata->mediaLinks = $mediaLinks;
260
        $odata->links = $links;
261
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
262
        $odata->baseURI = $baseURI;
1 ignored issue
show
Bug introduced by
The property baseURI 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...
263
264
        $newCount = count($this->lightStack);
265
        assert(
266
            $newCount == $stackCount,
267
            'Should have ' . $stackCount . ' elements in stack, have ' . $newCount . ' elements'
268
        );
269
        $this->lightStack[$newCount-1]['count']--;
270
        if (0 == $this->lightStack[$newCount-1]['count']) {
271
            array_pop($this->lightStack);
272
        }
273
        return $odata;
274
    }
275
276
    /**
277
     * Write top level feed element.
278
     *
279
     * @param QueryResult &$entryObjects Array of entry resources to be written
280
     *
281
     * @return ODataFeed
282
     */
283
    public function writeTopLevelElements(QueryResult &$entryObjects)
284
    {
285
        assert(is_array($entryObjects->results), '!is_array($entryObjects->results)');
286
287
        $this->loadStackIfEmpty();
288
        $setName = $this->getRequest()->getTargetResourceSetWrapper()->getName();
289
290
        $title = $this->getRequest()->getContainerName();
291
        $relativeUri = $this->getRequest()->getIdentifier();
292
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
293
294
        $selfLink = new ODataLink();
295
        $selfLink->name = 'self';
296
        $selfLink->title = $relativeUri;
297
        $selfLink->url = $relativeUri;
298
299
        $odata = new ODataFeed();
300
        $odata->title = new ODataTitle($title);
301
        $odata->id = $absoluteUri;
302
        $odata->selfLink = $selfLink;
303
        $odata->updated = $this->getUpdated()->format(DATE_ATOM);
304
        $odata->baseURI = $this->isBaseWritten ? null : $this->absoluteServiceUriWithSlash;
1 ignored issue
show
Bug introduced by
The property baseURI 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...
305
        $this->isBaseWritten = true;
306
307
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
308
            $odata->rowCount = $this->getRequest()->getCountValue();
309
        }
310
        foreach ($entryObjects->results as $entry) {
311
            if (!$entry instanceof QueryResult) {
312
                $query = new QueryResult();
313
                $query->results = $entry;
314
            } else {
315
                $query = $entry;
316
            }
317
            $odata->entries[] = $this->writeTopLevelElement($query);
318
        }
319
320
        $resourceSet = $this->getRequest()->getTargetResourceSetWrapper()->getResourceSet();
321
        $requestTop = $this->getRequest()->getTopOptionCount();
322
        $pageSize = $this->getService()->getConfiguration()->getEntitySetPageSize($resourceSet);
323
        $requestTop = (null === $requestTop) ? $pageSize + 1 : $requestTop;
324
325
        if (true === $entryObjects->hasMore && $requestTop > $pageSize) {
326
            $stackSegment = $setName;
327
            $lastObject = end($entryObjects->results);
328
            $segment = $this->getNextLinkUri($lastObject);
329
            $nextLink = new ODataLink();
330
            $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
331
            $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
332
            $odata->nextPageLink = $nextLink;
333
        }
334
335
        return $odata;
336
    }
337
338
    /**
339
     * Write top level url element.
340
     *
341
     * @param QueryResult $entryObject The entry resource whose url to be written
342
     *
343
     * @return ODataURL
344
     */
345
    public function writeUrlElement(QueryResult $entryObject)
346
    {
347
        $url = new ODataURL();
348
        if (!is_null($entryObject->results)) {
349
            $currentResourceType = $this->getCurrentResourceSetWrapper()->getResourceType();
350
            $relativeUri = $this->getEntryInstanceKey(
351
                $entryObject->results,
352
                $currentResourceType,
353
                $this->getCurrentResourceSetWrapper()->getName()
354
            );
355
356
            $url->url = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
357
        }
358
359
        return $url;
360
    }
361
362
    /**
363
     * Write top level url collection.
364
     *
365
     * @param QueryResult $entryObjects Array of entry resources whose url to be written
366
     *
367
     * @return ODataURLCollection
368
     */
369
    public function writeUrlElements(QueryResult $entryObjects)
370
    {
371
        $urls = new ODataURLCollection();
372
        if (!empty($entryObjects->results)) {
373
            $i = 0;
374
            foreach ($entryObjects->results as $entryObject) {
375
                $urls->urls[$i] = $this->writeUrlElement($entryObject);
376
                ++$i;
377
            }
378
379
            if ($i > 0 && true === $entryObjects->hasMore) {
380
                $stackSegment = $this->getRequest()->getTargetResourceSetWrapper()->getName();
381
                $lastObject = end($entryObjects->results);
382
                $segment = $this->getNextLinkUri($lastObject);
383
                $nextLink = new ODataLink();
384
                $nextLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
385
                $nextLink->url = rtrim($this->absoluteServiceUri, '/') . '/' . $stackSegment . $segment;
386
                $urls->nextPageLink = $nextLink;
387
            }
388
        }
389
390
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
391
            $urls->count = $this->getRequest()->getCountValue();
392
        }
393
394
        return $urls;
395
    }
396
397
    /**
398
     * Write top level complex resource.
399
     *
400
     * @param QueryResult &$complexValue The complex object to be written
401
     * @param string $propertyName The name of the complex property
402
     * @param ResourceType &$resourceType Describes the type of complex object
403
     *
404
     * @return ODataPropertyContent
405
     */
406
    public function writeTopLevelComplexObject(QueryResult &$complexValue, $propertyName, ResourceType &$resourceType)
407
    {
408
        $result = $complexValue->results;
409
410
        $propertyContent = new ODataPropertyContent();
411
        $odataProperty = new ODataProperty();
412
        $odataProperty->name = $propertyName;
413
        $odataProperty->typeName = $resourceType->getFullName();
414
        if (null != $result) {
415
            $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...
416
            $odataProperty->value = $internalContent;
417
        }
418
419
        $propertyContent->properties[$propertyName] = $odataProperty;
420
421
        return $propertyContent;
422
    }
423
424
    /**
425
     * Write top level bag resource.
426
     *
427
     * @param QueryResult &$BagValue The bag object to be
428
     *                                    written
429
     * @param string $propertyName The name of the
430
     *                                    bag property
431
     * @param ResourceType &$resourceType Describes the type of
432
     *                                    bag object
433
     * @return ODataPropertyContent
434
     */
435
    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...
436
    {
437
        $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...
438
439
        $propertyContent = new ODataPropertyContent();
440
        $odataProperty = new ODataProperty();
441
        $odataProperty->name = $propertyName;
442
        $odataProperty->typeName = 'Collection('.$resourceType->getFullName().')';
443
        $odataProperty->value = $this->writeBagValue($resourceType, $result);
444
445
        $propertyContent->properties[$propertyName] = $odataProperty;
446
        return $propertyContent;
447
    }
448
449
    /**
450
     * Write top level primitive value.
451
     *
452
     * @param QueryResult &$primitiveValue              The primitive value to be
453
     *                                            written
454
     * @param ResourceProperty &$resourceProperty Resource property describing the
455
     *                                            primitive property to be written
456
     * @return ODataPropertyContent
457
     */
458
    public function writeTopLevelPrimitive(QueryResult &$primitiveValue, ResourceProperty &$resourceProperty = null)
459
    {
460
        assert(null != $resourceProperty, 'Resource property must not be null');
461
        $propertyContent = new ODataPropertyContent();
462
463
        $odataProperty = new ODataProperty();
464
        $odataProperty->name = $resourceProperty->getName();
465
        $iType = $resourceProperty->getInstanceType();
466
        assert($iType instanceof IType, get_class($iType));
467
        $odataProperty->typeName = $iType->getFullTypeName();
468
        if (null == $primitiveValue->results) {
469
            $odataProperty->value = null;
470
        } else {
471
            $rType = $resourceProperty->getResourceType()->getInstanceType();
472
            assert($rType instanceof IType, get_class($rType));
473
            $odataProperty->value = $this->primitiveToString($rType, $primitiveValue->results);
474
        }
475
476
        $propertyContent->properties[$odataProperty->name] = $odataProperty;
477
478
        return $propertyContent;
479
    }
480
481
    /**
482
     * Gets reference to the request submitted by client.
483
     *
484
     * @return RequestDescription
485
     */
486
    public function getRequest()
487
    {
488
        assert(null != $this->request, 'Request not yet set');
489
490
        return $this->request;
491
    }
492
493
    /**
494
     * Sets reference to the request submitted by client.
495
     *
496
     * @param RequestDescription $request
497
     */
498
    public function setRequest(RequestDescription $request)
499
    {
500
        $this->request = $request;
501
        $this->stack->setRequest($request);
502
    }
503
504
    /**
505
     * Gets the data service instance.
506
     *
507
     * @return IService
508
     */
509
    public function getService()
510
    {
511
        return $this->service;
512
    }
513
514
    /**
515
     * Gets the segment stack instance.
516
     *
517
     * @return SegmentStack
518
     */
519
    public function getStack()
520
    {
521
        return $this->stack;
522
    }
523
524
    /**
525
     * Get update timestamp
526
     *
527
     * @return Carbon
528
     */
529
    public function getUpdated()
530
    {
531
        return $this->updated;
532
    }
533
534
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
535
    {
536
        $typeName = $resourceType->getName();
537
        $keyProperties = $resourceType->getKeyProperties();
538
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
539
        $keyString = $containerName . '(';
540
        $comma = null;
541
        foreach ($keyProperties as $keyName => $resourceProperty) {
542
            $keyType = $resourceProperty->getInstanceType();
543
            assert($keyType instanceof IType, '$keyType not instanceof IType');
544
            $keyName = $resourceProperty->getName();
545
            $keyValue = (self::PK == $keyName) ? $entityInstance->getKey() : $entityInstance->$keyName;
546
            if (!isset($keyValue)) {
547
                throw ODataException::createInternalServerError(
548
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
549
                );
550
            }
551
552
            $keyValue = $keyType->convertToOData($keyValue);
553
            $keyString .= $comma . $keyName . '=' . $keyValue;
554
            $comma = ',';
555
        }
556
557
        $keyString .= ')';
558
559
        return $keyString;
560
    }
561
562
    /**
563
     * @param $entryObject
564
     * @param $type
565
     * @param $relativeUri
566
     * @param $resourceType
567
     * @return array<ODataMediaLink|null|array>
568
     */
569
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
570
    {
571
        $context = $this->getService()->getOperationContext();
572
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
573
        assert(null != $streamProviderWrapper, 'Retrieved stream provider must not be null');
574
575
        $mediaLink = null;
576
        if ($resourceType->isMediaLinkEntry()) {
577
            $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...
578
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag, 'edit-media');
579
        }
580
        $mediaLinks = [];
581
        if ($resourceType->hasNamedStream()) {
582
            $namedStreams = $resourceType->getAllNamedStreams();
583
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
584
                $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...
585
                    $entryObject,
586
                    $resourceStreamInfo,
587
                    $context,
588
                    $relativeUri
589
                );
590
                $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...
591
                    $entryObject,
592
                    $resourceStreamInfo,
593
                    $context
594
                );
595
                $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...
596
                    $entryObject,
597
                    $resourceStreamInfo,
598
                    $context
599
                );
600
601
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
602
                $mediaLinks[] = $nuLink;
603
            }
604
        }
605
        return [$mediaLink, $mediaLinks];
606
    }
607
608
    /**
609
     * Gets collection of projection nodes under the current node.
610
     *
611
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes describing projections for the current
612
     *                                                        segment, If this method returns null it means no
613
     *                                                        projections are to be applied and the entire resource for
614
     *                                                        the current segment should be serialized, If it returns
615
     *                                                        non-null only the properties described by the returned
616
     *                                                        projection segments should be serialized
617
     */
618
    protected function getProjectionNodes()
619
    {
620
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
621
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
622
            return null;
623
        }
624
625
        return $expandedProjectionNode->getChildNodes();
626
    }
627
628
    /**
629
     * Find a 'ExpandedProjectionNode' instance in the projection tree
630
     * which describes the current segment.
631
     *
632
     * @return null|RootProjectionNode|ExpandedProjectionNode
633
     */
634
    protected function getCurrentExpandedProjectionNode()
635
    {
636
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
637
        if (is_null($expandedProjectionNode)) {
638
            return null;
639
        } else {
640
            $segmentNames = $this->getLightStack();
641
            $depth = count($segmentNames);
642
            // $depth == 1 means serialization of root entry
643
            //(the resource identified by resource path) is going on,
644
            //so control won't get into the below for loop.
645
            //we will directly return the root node,
646
            //which is 'ExpandedProjectionNode'
647
            // for resource identified by resource path.
648
            if (0 != $depth) {
649
                for ($i = 1; $i < $depth; ++$i) {
650
                    $expandedProjectionNode = $expandedProjectionNode->findNode($segmentNames[$i]['prop']);
651
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
652
                    assert(
653
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
654
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
655
                    );
656
                }
657
            }
658
        }
659
660
        return $expandedProjectionNode;
661
    }
662
663
    /**
664
     * Check whether to expand a navigation property or not.
665
     *
666
     * @param string $navigationPropertyName Name of naviagtion property in question
667
     *
668
     * @return bool True if the given navigation should be expanded, otherwise false
669
     */
670
    protected function shouldExpandSegment($navigationPropertyName)
671
    {
672
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
673
        if (is_null($expandedProjectionNode)) {
674
            return false;
675
        }
676
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
677
678
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
679
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
680
    }
681
682
    /**
683
     * Wheter next link is needed for the current resource set (feed)
684
     * being serialized.
685
     *
686
     * @param int $resultSetCount Number of entries in the current
687
     *                            resource set
688
     *
689
     * @return bool true if the feed must have a next page link
690
     */
691
    protected function needNextPageLink($resultSetCount)
692
    {
693
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
694
        $recursionLevel = count($this->getStack()->getSegmentNames());
695
        $pageSize = $currentResourceSet->getResourceSetPageSize();
696
697
        if (1 == $recursionLevel) {
698
            //presence of $top option affect next link for root container
699
            $topValueCount = $this->getRequest()->getTopOptionCount();
700
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
701
                return false;
702
            }
703
        }
704
        return $resultSetCount == $pageSize;
705
    }
706
707
    /**
708
     * Resource set wrapper for the resource being serialized.
709
     *
710
     * @return ResourceSetWrapper
711
     */
712
    protected function getCurrentResourceSetWrapper()
713
    {
714
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
715
        $count = count($segmentWrappers);
716
717
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
718
    }
719
720
    /**
721
     * Get next page link from the given entity instance.
722
     *
723
     * @param mixed &$lastObject Last object serialized to be
724
     *                            used for generating $skiptoken
725
     * @return string for the link for next page
726
     * @throws ODataException
727
     *
728
     */
729
    protected function getNextLinkUri(&$lastObject)
730
    {
731
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
732
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
733
        assert(null != $internalOrderByInfo);
734
        assert(is_object($internalOrderByInfo));
735
        assert($internalOrderByInfo instanceof InternalOrderByInfo, get_class($internalOrderByInfo));
736
        $numSegments = count($internalOrderByInfo->getOrderByPathSegments());
737
        $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
738
739
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
740
        assert(!is_null($skipToken), '!is_null($skipToken)');
741
        $token = (1 < $numSegments) ? '$skiptoken=' : '$skip=';
742
        $skipToken = '?'.$queryParameterString.$token.$skipToken;
743
744
        return $skipToken;
745
    }
746
747
    /**
748
     * Builds the string corresponding to query parameters for top level results
749
     * (result set identified by the resource path) to be put in next page link.
750
     *
751
     * @return string|null string representing the query parameters in the URI
752
     *                     query parameter format, NULL if there
753
     *                     is no query parameters
754
     *                     required for the next link of top level result set
755
     */
756
    protected function getNextPageLinkQueryParametersForRootResourceSet()
757
    {
758
        $queryParameterString = null;
759
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
760
                     ODataConstants::HTTPQUERY_STRING_EXPAND,
761
                     ODataConstants::HTTPQUERY_STRING_ORDERBY,
762
                     ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
763
                     ODataConstants::HTTPQUERY_STRING_SELECT, ] as $queryOption) {
764
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
765
            if (!is_null($value)) {
766
                if (!is_null($queryParameterString)) {
767
                    $queryParameterString = $queryParameterString . '&';
768
                }
769
770
                $queryParameterString .= $queryOption . '=' . $value;
771
            }
772
        }
773
774
        $topCountValue = $this->getRequest()->getTopOptionCount();
775
        if (!is_null($topCountValue)) {
776
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
777
            if (0 < $remainingCount) {
778
                if (!is_null($queryParameterString)) {
779
                    $queryParameterString .= '&';
780
                }
781
782
                $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
783
            }
784
        }
785
786
        if (!is_null($queryParameterString)) {
787
            $queryParameterString .= '&';
788
        }
789
790
        return $queryParameterString;
791
    }
792
793
    private function loadStackIfEmpty()
794
    {
795
        if (0 == count($this->lightStack)) {
796
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
797
            array_push($this->lightStack, ['type' => $typeName, 'property' => $typeName, 'count' => 1]);
798
        }
799
    }
800
801
    /**
802
     * Convert the given primitive value to string.
803
     * Note: This method will not handle null primitive value.
804
     *
805
     * @param IType &$primitiveResourceType        Type of the primitive property
806
     *                                             whose value need to be converted
807
     * @param mixed        $primitiveValue         Primitive value to convert
808
     *
809
     * @return string
810
     */
811
    private function primitiveToString(IType &$type, $primitiveValue)
812
    {
813
        if ($type instanceof Boolean) {
814
            $stringValue = (true === $primitiveValue) ? 'true' : 'false';
815
        } elseif ($type instanceof Binary) {
816
            $stringValue = base64_encode($primitiveValue);
817
        } elseif ($type instanceof DateTime && $primitiveValue instanceof \DateTime) {
818
            $stringValue = $primitiveValue->format(\DateTime::ATOM);
819
        } elseif ($type instanceof StringType) {
820
            $stringValue = utf8_encode($primitiveValue);
821
        } else {
822
            $stringValue = strval($primitiveValue);
823
        }
824
825
        return $stringValue;
826
    }
827
828
    /**
829
     * @param $entryObject
830
     * @param $nonRelProp
831
     * @return ODataPropertyContent
832
     */
833
    private function writePrimitiveProperties($entryObject, $nonRelProp)
834
    {
835
        $propertyContent = new ODataPropertyContent();
836
        $cereal = $this->getModelSerialiser()->bulkSerialise($entryObject);
837
        foreach ($cereal as $corn => $flake) {
838
            if (!array_key_exists($corn, $nonRelProp)) {
839
                continue;
840
            }
841
            $corn = strval($corn);
842
            $rType = $nonRelProp[$corn]->getResourceType()->getInstanceType();
843
            $subProp = new ODataProperty();
844
            $subProp->name = $corn;
845
            $subProp->value = isset($flake) ? $this->primitiveToString($rType, $flake) : null;
846
            $subProp->typeName = $nonRelProp[$corn]->getResourceType()->getFullName();
847
            $propertyContent->properties[$corn] = $subProp;
848
        }
849
        return $propertyContent;
850
    }
851
852
    /**
853
     * @param $entryObject
854
     * @param $prop
855
     * @param $nuLink
856
     * @param $propKind
857
     * @param $propName
858
     */
859
    private function expandNavigationProperty(QueryResult $entryObject, $prop, $nuLink, $propKind, $propName)
860
    {
861
        $nextName = $prop->getResourceType()->getName();
862
        $nuLink->isExpanded = true;
863
        $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
864
        $nuLink->isCollection = $isCollection;
865
        $value = $entryObject->results->$propName;
866
867
        $result = new QueryResult();
868
        $result->results = $value;
869
        $resultCount = $isCollection ? count($value) : 1;
870
        $newStackLine = ['type' => $nextName, 'prop' => $propName, 'count' => $resultCount];
871
        array_push($this->lightStack, $newStackLine);
872
873
        if (!$isCollection) {
874
            $expandedResult = $this->writeTopLevelElement($result);
875
        } else {
876
            $expandedResult = $this->writeTopLevelElements($result);
877
        }
878
        $nuLink->expandedResult = $expandedResult;
879
        if (!isset($nuLink->expandedResult)) {
880
            $nuLink->isCollection = null;
881
            $nuLink->isExpanded = null;
882
        } else {
883
            if (isset($nuLink->expandedResult->selfLink)) {
884
                $nuLink->expandedResult->selfLink->title = $propName;
885
                $nuLink->expandedResult->selfLink->url = $nuLink->url;
886
                $nuLink->expandedResult->title = new ODataTitle($propName);
887
                $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
888
            }
889
        }
890
    }
891
892
    /**
893
     * Gets the data service instance.
894
     *
895
     * @return void
896
     */
897
    public function setService(IService $service)
898
    {
899
        $this->service = $service;
900
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
901
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
902
    }
903
904
    /**
905
     * @param ResourceType $resourceType
906
     * @param $result
907
     * @return ODataBagContent|null
908
     */
909
    protected function writeBagValue(ResourceType &$resourceType, $result)
910
    {
911
        assert(null == $result || is_array($result), 'Bag parameter must be null or array');
912
        $typeKind = $resourceType->getResourceTypeKind();
913
        assert(
914
            ResourceTypeKind::PRIMITIVE() == $typeKind || ResourceTypeKind::COMPLEX() == $typeKind,
915
            '$bagItemResourceTypeKind != ResourceTypeKind::PRIMITIVE'
916
            .' && $bagItemResourceTypeKind != ResourceTypeKind::COMPLEX'
917
        );
918
        if (null == $result) {
919
            return null;
920
        }
921
        $bag = new ODataBagContent();
922
        foreach ($result as $value) {
923
            if (isset($value)) {
924
                if (ResourceTypeKind::PRIMITIVE() == $typeKind) {
925
                    $instance = $resourceType->getInstanceType();
926
                    assert($instance instanceof IType, get_class($instance));
927
                    $bag->propertyContents[] = $this->primitiveToString($instance, $value);
928
                } elseif (ResourceTypeKind::COMPLEX() == $typeKind) {
929
                    $bag->propertyContents[] = $this->writeComplexValue($resourceType, $value);
930
                }
931
            }
932
        }
933
        return $bag;
934
    }
935
936
    /**
937
     * @param ResourceType  $resourceType
938
     * @param object        $result
939
     * @param string|null   $propertyName
940
     * @return ODataPropertyContent
941
     */
942
    protected function writeComplexValue(ResourceType &$resourceType, &$result, $propertyName = null)
943
    {
944
        assert(is_object($result), 'Supplied $customObject must be an object');
945
946
        $count = count($this->complexTypeInstanceCollection);
947
        for ($i = 0; $i < $count; ++$i) {
948
            if ($this->complexTypeInstanceCollection[$i] === $result) {
949
                throw new InvalidOperationException(
950
                    Messages::objectModelSerializerLoopsNotAllowedInComplexTypes($propertyName)
951
                );
952
            }
953
        }
954
955
        $this->complexTypeInstanceCollection[$count] = &$result;
956
957
        $internalContent = new ODataPropertyContent();
958
        $resourceProperties = $resourceType->getAllProperties();
959
        // first up, handle primitive properties
960
        foreach ($resourceProperties as $prop) {
961
            $resourceKind = $prop->getKind();
962
            $propName = $prop->getName();
963
            $internalProperty = new ODataProperty();
964
            $internalProperty->name = $propName;
965
            if (static::isMatchPrimitive($resourceKind)) {
966
                $iType = $prop->getInstanceType();
967
                assert($iType instanceof IType, get_class($iType));
968
                $internalProperty->typeName = $iType->getFullTypeName();
969
970
                $rType = $prop->getResourceType()->getInstanceType();
971
                assert($rType instanceof IType, get_class($rType));
972
                $internalProperty->value = $this->primitiveToString($rType, $result->$propName);
973
974
                $internalContent->properties[$propName] = $internalProperty;
975
            } elseif (ResourcePropertyKind::COMPLEX_TYPE == $resourceKind) {
976
                $rType = $prop->getResourceType();
977
                $internalProperty->typeName = $rType->getFullName();
978
                $internalProperty->value = $this->writeComplexValue($rType, $result->$propName, $propName);
979
980
                $internalContent->properties[$propName] = $internalProperty;
981
            }
982
        }
983
984
        unset($this->complexTypeInstanceCollection[$count]);
985
        return $internalContent;
986
    }
987
988
    public static function isMatchPrimitive($resourceKind)
989
    {
990
        if (16 > $resourceKind) {
991
            return false;
992
        }
993
        if (28 < $resourceKind) {
994
            return false;
995
        }
996
        return 0 == ($resourceKind % 4);
997
    }
998
999
    /*
1000
     * @return IMetadataProvider
1001
     */
1002
    protected function getMetadata()
1003
    {
1004
        if (null == $this->metaProvider) {
1005
            $this->metaProvider = App::make('metadata');
1006
        }
1007
        return $this->metaProvider;
1008
    }
1009
1010
    /**
1011
     * @return array
1012
     */
1013
    protected function getLightStack()
1014
    {
1015
        return $this->lightStack;
1016
    }
1017
1018
    /**
1019
     * @return ModelSerialiser
1020
     */
1021
    public function getModelSerialiser()
1022
    {
1023
        return $this->modelSerialiser;
1024
    }
1025
}
1026