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

IronicSerialiser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 2
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
        $requestTargetSource = $this->getRequest()->getTargetSource();
0 ignored issues
show
Unused Code introduced by
$requestTargetSource is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
221
222
        $title = $this->getRequest()->getContainerName();
223
        $relativeUri = $this->getRequest()->getIdentifier();
224
        $absoluteUri = $this->getRequest()->getRequestUrl()->getUrlAsString();
225
226
        $selfLink = new ODataLink();
227
        $selfLink->name = 'self';
228
        $selfLink->title = $relativeUri;
229
        $selfLink->url = $relativeUri;
230
231
        $odata = new ODataFeed();
232
        $odata->title = $title;
233
        $odata->id = $absoluteUri;
234
        $odata->selfLink = $selfLink;
235
236
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
237
            $odata->rowCount = $this->getRequest()->getCountValue();
238
        }
239
        foreach ($entryObjects as $entry) {
240
            $odata->entries[] = $this->writeTopLevelElement($entry);
241
        }
242
243
        return $odata;
244
    }
245
246
    /**
247
     * Write top level url element.
248
     *
249
     * @param mixed $entryObject The entry resource whose url to be written
250
     *
251
     * @return ODataURL
252
     */
253
    public function writeUrlElement($entryObject)
254
    {
255
        // TODO: Implement writeUrlElement() method.
256
    }
257
258
    /**
259
     * Write top level url collection.
260
     *
261
     * @param array $entryObjects Array of entry resources
262
     *                            whose url to be written
263
     *
264
     * @return ODataURLCollection
265
     */
266
    public function writeUrlElements($entryObjects)
267
    {
268
        // TODO: Implement writeUrlElements() method.
269
    }
270
271
    /**
272
     * Write top level complex resource.
273
     *
274
     * @param mixed &$complexValue The complex object to be
275
     *                                    written
276
     * @param string $propertyName The name of the
277
     *                                    complex property
278
     * @param ResourceType &$resourceType Describes the type of
279
     *                                    complex object
280
     *
281
     * @return ODataPropertyContent
282
     */
283
    public function writeTopLevelComplexObject(&$complexValue, $propertyName, ResourceType &$resourceType)
284
    {
285
        // TODO: Implement writeTopLevelComplexObject() method.
286
    }
287
288
    /**
289
     * Write top level bag resource.
290
     *
291
     * @param mixed &$BagValue The bag object to be
292
     *                                    written
293
     * @param string $propertyName The name of the
294
     *                                    bag property
295
     * @param ResourceType &$resourceType Describes the type of
296
     *                                    bag object
297
     *
298
     * @return ODataPropertyContent
299
     */
300
    public function writeTopLevelBagObject(&$BagValue, $propertyName, ResourceType &$resourceType)
301
    {
302
        // TODO: Implement writeTopLevelBagObject() method.
303
    }
304
305
    /**
306
     * Write top level primitive value.
307
     *
308
     * @param mixed &$primitiveValue The primitve value to be
309
     *                                            written
310
     * @param ResourceProperty &$resourceProperty Resource property
311
     *                                            describing the
312
     *                                            primitive property
313
     *                                            to be written
314
     *
315
     * @return ODataPropertyContent
316
     */
317
    public function writeTopLevelPrimitive(&$primitiveValue, ResourceProperty &$resourceProperty = null)
318
    {
319
        // TODO: Implement writeTopLevelPrimitive() method.
320
    }
321
322
    /**
323
     * Gets reference to the request submitted by client.
324
     *
325
     * @return RequestDescription
326
     */
327
    public function getRequest()
328
    {
329
        assert(null != $this->request, 'Request not yet set');
330
331
        return $this->request;
332
    }
333
334
    /**
335
     * Sets reference to the request submitted by client.
336
     *
337
     * @param RequestDescription $request
338
     */
339
    public function setRequest(RequestDescription $request)
340
    {
341
        $this->request = $request;
342
        $this->stack->setRequest($request);
343
    }
344
345
    /**
346
     * Gets the data service instance.
347
     *
348
     * @return IService
349
     */
350
    public function getService()
351
    {
352
        return $this->service;
353
    }
354
355
    /**
356
     * Gets the segment stack instance.
357
     *
358
     * @return SegmentStack
359
     */
360
    public function getStack()
361
    {
362
        return $this->stack;
363
    }
364
365
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
366
    {
367
        $typeName = $resourceType->getName();
368
        $keyProperties = $resourceType->getKeyProperties();
369
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
370
        $keyString = $containerName . '(';
371
        $comma = null;
372
        foreach ($keyProperties as $keyName => $resourceProperty) {
373
            $keyType = $resourceProperty->getInstanceType();
374
            assert($keyType instanceof IType, '$keyType not instanceof IType');
375
            $keyName = $resourceProperty->getName();
376
            $keyValue = $entityInstance->$keyName;
377
            if (!isset($keyValue)) {
378
                throw ODataException::createInternalServerError(
379
                    Messages::badQueryNullKeysAreNotSupported($typeName, $keyName)
380
                );
381
            }
382
383
            $keyValue = $keyType->convertToOData($keyValue);
384
            $keyString .= $comma . $keyName . '=' . $keyValue;
385
            $comma = ',';
386
        }
387
388
        $keyString .= ')';
389
390
        return $keyString;
391
392
    }
393
394
    /**
395
     * @param $entryObject
396
     * @param $type
397
     * @param $relativeUri
398
     * @param $resourceType
399
     * @return array
400
     */
401
    protected function writeMediaData($entryObject, $type, $relativeUri, ResourceType $resourceType)
402
    {
403
        $context = $this->getService()->getOperationContext();
404
        $streamProviderWrapper = $this->getService()->getStreamProviderWrapper();
405
        assert(null != $streamProviderWrapper, "Retrieved stream provider must not be null");
406
407
        $mediaLink = null;
408
        if ($resourceType->isMediaLinkEntry()) {
409
            $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...
410
            $mediaLink = new ODataMediaLink($type, '/$value', $relativeUri . '/$value', '*/*', $eTag);
411
        }
412
        $mediaLinks = [];
413
        if ($resourceType->hasNamedStream()) {
414
            $namedStreams = $resourceType->getAllNamedStreams();
415
            foreach ($namedStreams as $streamTitle => $resourceStreamInfo) {
416
                $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...
417
                    $entryObject,
418
                    $resourceStreamInfo,
419
                    $context,
420
                    $relativeUri
421
                );
422
                $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...
423
                    $entryObject,
424
                    $resourceStreamInfo,
425
                    $context
426
                );
427
                $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...
428
                    $entryObject,
429
                    $resourceStreamInfo,
430
                    $context
431
                );
432
433
                $nuLink = new ODataMediaLink($streamTitle, $readUri, $readUri, $mediaContentType, $eTag);
434
                $mediaLinks[] = $nuLink;
435
            }
436
        }
437
        return [$mediaLink, $mediaLinks];
438
    }
439
440
    /**
441
     * Gets collection of projection nodes under the current node.
442
     *
443
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes
444
     *                                                        describing projections for the current segment, If this method returns
445
     *                                                        null it means no projections are to be applied and the entire resource
446
     *                                                        for the current segment should be serialized, If it returns non-null
447
     *                                                        only the properties described by the returned projection segments should
448
     *                                                        be serialized
449
     */
450
    protected function getProjectionNodes()
451
    {
452
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
453
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
454
            return null;
455
        }
456
457
        return $expandedProjectionNode->getChildNodes();
458
    }
459
460
    /**
461
     * Find a 'ExpandedProjectionNode' instance in the projection tree
462
     * which describes the current segment.
463
     *
464
     * @return ExpandedProjectionNode|null
465
     */
466
    protected function getCurrentExpandedProjectionNode()
467
    {
468
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
469
        if (is_null($expandedProjectionNode)) {
470
            return null;
471
        } else {
472
            $segmentNames = $this->getStack()->getSegmentNames();
473
            $depth = count($segmentNames);
474
            // $depth == 1 means serialization of root entry
475
            //(the resource identified by resource path) is going on,
476
            //so control won't get into the below for loop.
477
            //we will directly return the root node,
478
            //which is 'ExpandedProjectionNode'
479
            // for resource identified by resource path.
480
            if ($depth != 0) {
481
                for ($i = 1; $i < $depth; ++$i) {
482
                    $expandedProjectionNode
483
                        = $expandedProjectionNode->findNode($segmentNames[$i]);
484
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
485
                    assert(
486
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
487
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
488
                    );
489
                }
490
            }
491
        }
492
493
        return $expandedProjectionNode;
494
    }
495
496
    /**
497
     * Check whether to expand a navigation property or not.
498
     *
499
     * @param string $navigationPropertyName Name of naviagtion property in question
500
     *
501
     * @return bool True if the given navigation should be
502
     *              explanded otherwise false
503
     */
504
    protected function shouldExpandSegment($navigationPropertyName)
505
    {
506
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
507
        if (is_null($expandedProjectionNode)) {
508
            return false;
509
        }
510
511
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
512
513
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
514
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
515
    }
516
}
517