Test Failed
Push — master ( b488b6...028484 )
by Alex
01:42
created

IronicSerialiser::writeMediaData()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 38
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

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