Test Failed
Push — master ( d2ea3d...f6e322 )
by Alex
03:46
created

IronicSerialiser::primitiveToString()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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