Test Setup Failed
Pull Request — master (#62)
by Alex
03:37
created

IronicSerialiser::getProjectionNodes()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 3
eloc 5
nc 2
nop 0
1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Serialisers;
4
5
use POData\Common\Messages;
6
use POData\Common\ODataConstants;
7
use POData\Common\ODataException;
8
use POData\IService;
9
use POData\ObjectModel\IObjectSerialiser;
10
use POData\ObjectModel\ODataEntry;
11
use POData\ObjectModel\ODataFeed;
12
use POData\ObjectModel\ODataLink;
13
use POData\ObjectModel\ODataMediaLink;
14
use POData\ObjectModel\ODataNavigationPropertyInfo;
15
use POData\ObjectModel\ODataPropertyContent;
16
use POData\ObjectModel\ODataURL;
17
use POData\ObjectModel\ODataURLCollection;
18
use POData\Providers\Metadata\ResourceEntityType;
19
use POData\Providers\Metadata\ResourceProperty;
20
use POData\Providers\Metadata\ResourcePropertyKind;
21
use POData\Providers\Metadata\ResourceSet;
22
use POData\Providers\Metadata\ResourceType;
23
use POData\Providers\Metadata\Type\IType;
24
use POData\Providers\Query\QueryType;
25
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
26
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ProjectionNode;
27
use POData\UriProcessor\RequestDescription;
28
use POData\UriProcessor\SegmentStack;
29
30
class IronicSerialiser implements IObjectSerialiser
31
{
32
    /**
33
     * The service implementation.
34
     *
35
     * @var IService
36
     */
37
    protected $service;
38
39
    /**
40
     * Request description instance describes OData request the
41
     * the client has submitted and result of the request.
42
     *
43
     * @var RequestDescription
44
     */
45
    protected $request;
46
47
    /**
48
     * Collection of complex type instances used for cycle detection.
49
     *
50
     * @var array
51
     */
52
    protected $complexTypeInstanceCollection;
53
54
    /**
55
     * Absolute service Uri.
56
     *
57
     * @var string
58
     */
59
    protected $absoluteServiceUri;
60
61
    /**
62
     * Absolute service Uri with slash.
63
     *
64
     * @var string
65
     */
66
    protected $absoluteServiceUriWithSlash;
67
68
    /**
69
     * Holds reference to segment stack being processed.
70
     *
71
     * @var SegmentStack
72
     */
73
    protected $stack;
74
75
    /**
76
     * Lightweight stack tracking for recursive descent fill
77
     */
78
    private $lightStack = [];
79
80
    /**
81
     * @param IService           $service Reference to the data service instance
82
     * @param RequestDescription $request Type instance describing the client submitted request
83
     */
84
    public function __construct(IService $service, RequestDescription $request = null)
85
    {
86
        $this->service = $service;
87
        $this->request = $request;
88
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
89
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
90
        $this->stack = new SegmentStack($request);
91
        $this->complexTypeInstanceCollection = [];
92
    }
93
94
    /**
95
     * Write a top level entry resource.
96
     *
97
     * @param mixed $entryObject Reference to the entry object to be written
98
     *
99
     * @return ODataEntry
100
     */
101
    public function writeTopLevelElement($entryObject)
102
    {
103
        if (!isset($entryObject)) {
104
            array_pop($this->lightStack);
105
            return null;
106
        }
107
108
        if (0 == count($this->lightStack)) {
109
            $typeName = $this->getRequest()->getTargetResourceType()->getName();
110
            array_push($this->lightStack, [$typeName, $typeName]);
111
        }
112
113
        $stackCount = count($this->lightStack);
114
        $topOfStack = $this->lightStack[$stackCount-1];
115
        $resourceType = $this->getService()->getProvidersWrapper()->resolveResourceType($topOfStack[0]);
116
        $rawProp = $resourceType->getAllProperties();
117
        $relProp = [];
118
        foreach ($rawProp as $prop) {
119
            if ($prop->getResourceType() instanceof ResourceEntityType) {
120
                $relProp[] = $prop;
121
            }
122
        }
123
124
        $resourceSet = $resourceType->getCustomState();
125
        assert($resourceSet instanceof ResourceSet);
126
        $title = $resourceType->getName();
127
        $type = $resourceType->getFullName();
128
129
        $relativeUri = $this->getEntryInstanceKey(
130
            $entryObject,
131
            $resourceType,
132
            $resourceSet->getName()
133
        );
134
        $absoluteUri = rtrim($this->absoluteServiceUri, '/') . '/' . $relativeUri;
135
136
        list($mediaLink, $mediaLinks) = $this->writeMediaData($entryObject, $type, $relativeUri, $resourceType);
137
138
        $propertyContent = new ODataPropertyContent();
139
140
        $links = [];
141
        foreach ($relProp as $prop) {
142
            $nuLink = new ODataLink();
143
            $propKind = $prop->getKind();
144
145
            assert(
146
                ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind
147
                || ResourcePropertyKind::RESOURCE_REFERENCE == $propKind,
148
                '$propKind != ResourcePropertyKind::RESOURCESET_REFERENCE &&'
149
                .' $propKind != ResourcePropertyKind::RESOURCE_REFERENCE'
150
            );
151
            $propTail = ResourcePropertyKind::RESOURCE_REFERENCE == $propKind ? 'entry' : 'feed';
152
            $propType = 'application/atom+xml;type='.$propTail;
153
            $propName = $prop->getName();
154
            $nuLink->title = $propName;
155
            $nuLink->name = ODataConstants::ODATA_RELATED_NAMESPACE . $propName;
156
            $nuLink->url = $relativeUri . '/' . $propName;
157
            $nuLink->type = $propType;
158
159
            $navProp = new ODataNavigationPropertyInfo($prop, $this->shouldExpandSegment($propName));
160
            if ($navProp->expanded) {
161
                $nextName = $prop->getResourceType()->getName();
162
                $nuLink->isExpanded = true;
163
                $isCollection = ResourcePropertyKind::RESOURCESET_REFERENCE == $propKind;
164
                $nuLink->isCollection = $isCollection;
165
                $value = $entryObject->$propName;
166
                array_push($this->lightStack, [$nextName, $propName]);
167
                if (!$isCollection) {
168
                    $expandedResult = $this->writeTopLevelElement($value);
169
                } else {
170
                    $expandedResult = $this->writeTopLevelElements($value);
171
                }
172
                $nuLink->expandedResult = $expandedResult;
173
                if (!isset($nuLink->expandedResult)) {
174
                    $nuLink->isCollection = null;
175
                    $nuLink->isExpanded = null;
176
                } else {
177
                    if (isset($nuLink->expandedResult->selfLink)) {
178
                        $nuLink->expandedResult->selfLink->title = $propName;
179
                        $nuLink->expandedResult->selfLink->url = $nuLink->url;
180
                        $nuLink->expandedResult->title = $propName;
181
                        $nuLink->expandedResult->id = rtrim($this->absoluteServiceUri, '/') . '/' . $nuLink->url;
182
                    }
183
                }
184
            }
185
186
            $links[] = $nuLink;
187
        }
188
189
        $odata = new ODataEntry();
190
        $odata->resourceSetName = $resourceSet->getName();
191
        $odata->id = $absoluteUri;
192
        $odata->title = $title;
193
        $odata->type = $type;
194
        $odata->propertyContent = $propertyContent;
195
        $odata->isMediaLinkEntry = $resourceType->isMediaLinkEntry();
196
        $odata->editLink = $relativeUri;
197
        $odata->mediaLink = $mediaLink;
198
        $odata->mediaLinks = $mediaLinks;
199
        $odata->links = $links;
200
201
        $newCount = count($this->lightStack);
202
        assert($newCount == $stackCount, "Should have $stackCount elements in stack, have $newCount elements");
203
        array_pop($this->lightStack);
204
        return $odata;
205
    }
206
207
    /**
208
     * Write top level feed element.
209
     *
210
     * @param array &$entryObjects Array of entry resources to be written
211
     *
212
     * @return ODataFeed
213
     */
214
    public function writeTopLevelElements(&$entryObjects)
215
    {
216
        if (!isset($entryObjects) || !is_array($entryObjects) || 0 == count($entryObjects)) {
217
            return null;
218
        }
219
        assert(is_array($entryObjects), '!is_array($entryObjects)');
220
221
        $title = $this->getRequest()->getContainerName();
222
        $relativeUri = $this->getRequest()->getIdentifier();
223
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
224
225
        $selfLink = new ODataLink();
226
        $selfLink->name = 'self';
227
        $selfLink->title = $relativeUri;
228
        $selfLink->url = $relativeUri;
229
230
        $odata = new ODataFeed();
231
        $odata->title = $title;
232
        $odata->id = $absoluteUri;
233
        $odata->selfLink = $selfLink;
234
235
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
236
            $odata->rowCount = $this->getRequest()->getCountValue();
237
        }
238
        foreach ($entryObjects as $entry) {
239
            $odata->entries[] = $this->writeTopLevelElement($entry);
240
        }
241
242
        return $odata;
243
    }
244
245
    /**
246
     * Write top level url element.
247
     *
248
     * @param mixed $entryObject The entry resource whose url to be written
249
     *
250
     * @return ODataURL
251
     */
252
    public function writeUrlElement($entryObject)
253
    {
254
        // TODO: Implement writeUrlElement() method.
255
    }
256
257
    /**
258
     * Write top level url collection.
259
     *
260
     * @param array $entryObjects Array of entry resources
261
     *                            whose url to be written
262
     *
263
     * @return ODataURLCollection
264
     */
265
    public function writeUrlElements($entryObjects)
266
    {
267
        // TODO: Implement writeUrlElements() method.
268
    }
269
270
    /**
271
     * Write top level complex resource.
272
     *
273
     * @param mixed &$complexValue The complex object to be
274
     *                                    written
275
     * @param string $propertyName The name of the
276
     *                                    complex property
277
     * @param ResourceType &$resourceType Describes the type of
278
     *                                    complex object
279
     *
280
     * @return ODataPropertyContent
281
     */
282
    public function writeTopLevelComplexObject(&$complexValue, $propertyName, ResourceType &$resourceType)
283
    {
284
        // TODO: Implement writeTopLevelComplexObject() method.
285
    }
286
287
    /**
288
     * Write top level bag resource.
289
     *
290
     * @param mixed &$BagValue The bag object to be
291
     *                                    written
292
     * @param string $propertyName The name of the
293
     *                                    bag property
294
     * @param ResourceType &$resourceType Describes the type of
295
     *                                    bag object
296
     *
297
     * @return ODataPropertyContent
298
     */
299
    public function writeTopLevelBagObject(&$BagValue, $propertyName, ResourceType &$resourceType)
300
    {
301
        // TODO: Implement writeTopLevelBagObject() method.
302
    }
303
304
    /**
305
     * Write top level primitive value.
306
     *
307
     * @param mixed &$primitiveValue The primitve value to be
308
     *                                            written
309
     * @param ResourceProperty &$resourceProperty Resource property
310
     *                                            describing the
311
     *                                            primitive property
312
     *                                            to be written
313
     *
314
     * @return ODataPropertyContent
315
     */
316
    public function writeTopLevelPrimitive(&$primitiveValue, ResourceProperty &$resourceProperty = null)
317
    {
318
        // TODO: Implement writeTopLevelPrimitive() method.
319
    }
320
321
    /**
322
     * Gets reference to the request submitted by client.
323
     *
324
     * @return RequestDescription
325
     */
326
    public function getRequest()
327
    {
328
        assert(null != $this->request, 'Request not yet set');
329
330
        return $this->request;
331
    }
332
333
    /**
334
     * Sets reference to the request submitted by client.
335
     *
336
     * @param RequestDescription $request
337
     */
338
    public function setRequest(RequestDescription $request)
339
    {
340
        $this->request = $request;
341
        $this->stack->setRequest($request);
342
    }
343
344
    /**
345
     * Gets the data service instance.
346
     *
347
     * @return IService
348
     */
349
    public function getService()
350
    {
351
        return $this->service;
352
    }
353
354
    /**
355
     * Gets the segment stack instance.
356
     *
357
     * @return SegmentStack
358
     */
359
    public function getStack()
360
    {
361
        return $this->stack;
362
    }
363
364
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
365
    {
366
        $typeName = $resourceType->getName();
367
        $keyProperties = $resourceType->getKeyProperties();
368
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
369
        $keyString = $containerName . '(';
370
        $comma = null;
371
        foreach ($keyProperties as $keyName => $resourceProperty) {
372
            $keyType = $resourceProperty->getInstanceType();
373
            assert($keyType instanceof IType, '$keyType not instanceof IType');
374
            $keyName = $resourceProperty->getName();
375
            $keyValue = $entityInstance->$keyName;
376
            if (!isset($keyValue)) {
377
                throw ODataException::createInternalServerError(
378
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
379
                );
380
            }
381
382
            $keyValue = $keyType->convertToOData($keyValue);
383
            $keyString .= $comma . $keyName . '=' . $keyValue;
384
            $comma = ',';
385
        }
386
387
        $keyString .= ')';
388
389
        return $keyString;
390
    }
391
392
    /**
393
     * @param $entryObject
394
     * @param $type
395
     * @param $relativeUri
396
     * @param $resourceType
397
     * @return array
398
     */
399
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
400
    {
401
        $context = $this->getService()->getOperationContext();
402
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
403
        assert(null != $streamProviderWrapper, "Retrieved stream provider must not be null");
404
405
        $mediaLink = null;
406
        if ($resourceType->isMediaLinkEntry()) {
407
            $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...
408
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag);
409
        }
410
        $mediaLinks = [];
411
        if ($resourceType->hasNamedStream()) {
412
            $namedStreams = $resourceType->getAllNamedStreams();
413
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
414
                $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...
415
                    $entryObject,
416
                    $resourceStreamInfo,
417
                    $context,
418
                    $relativeUri
419
                );
420
                $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...
421
                    $entryObject,
422
                    $resourceStreamInfo,
423
                    $context
424
                );
425
                $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...
426
                    $entryObject,
427
                    $resourceStreamInfo,
428
                    $context
429
                );
430
431
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
432
                $mediaLinks[] = $nuLink;
433
            }
434
        }
435
        return [$mediaLink, $mediaLinks];
436
    }
437
438
    /**
439
     * Gets collection of projection nodes under the current node.
440
     *
441
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes
442
     *                                                        describing projections for the current segment, If this method returns
443
     *                                                        null it means no projections are to be applied and the entire resource
444
     *                                                        for the current segment should be serialized, If it returns non-null
445
     *                                                        only the properties described by the returned projection segments should
446
     *                                                        be serialized
447
     */
448
    protected function getProjectionNodes()
449
    {
450
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
451
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
452
            return null;
453
        }
454
455
        return $expandedProjectionNode->getChildNodes();
456
    }
457
458
    /**
459
     * Find a 'ExpandedProjectionNode' instance in the projection tree
460
     * which describes the current segment.
461
     *
462
     * @return ExpandedProjectionNode|null
463
     */
464
    protected function getCurrentExpandedProjectionNode()
465
    {
466
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
467
        if (is_null($expandedProjectionNode)) {
468
            return null;
469
        } else {
470
            $segmentNames = $this->getStack()->getSegmentNames();
471
            $depth = count($segmentNames);
472
            // $depth == 1 means serialization of root entry
473
            //(the resource identified by resource path) is going on,
474
            //so control won't get into the below for loop.
475
            //we will directly return the root node,
476
            //which is 'ExpandedProjectionNode'
477
            // for resource identified by resource path.
478
            if ($depth != 0) {
479
                for ($i = 1; $i < $depth; ++$i) {
480
                    $expandedProjectionNode
481
                        = $expandedProjectionNode->findNode($segmentNames[$i]);
482
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
483
                    assert(
484
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
485
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
486
                    );
487
                }
488
            }
489
        }
490
491
        return $expandedProjectionNode;
492
    }
493
494
    /**
495
     * Check whether to expand a navigation property or not.
496
     *
497
     * @param string $navigationPropertyName Name of naviagtion property in question
498
     *
499
     * @return bool True if the given navigation should be
500
     *              explanded otherwise false
501
     */
502
    protected function shouldExpandSegment($navigationPropertyName)
503
    {
504
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
505
        if (is_null($expandedProjectionNode)) {
506
            return false;
507
        }
508
509
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
510
511
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
512
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
513
    }
514
}
515