Test Failed
Push — master ( 7228dc...961f38 )
by Bálint
12:45
created

ExpandedProjectionNode::sortNodes()   B

Complexity

Conditions 7
Paths 13

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 13
c 1
b 0
f 0
dl 0
loc 20
rs 8.8333
cc 7
nc 13
nop 0
1
<?php
2
3
namespace POData\UriProcessor\QueryProcessor\ExpandProjectionParser;
4
5
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
6
use POData\Providers\Metadata\ResourceType;
7
use POData\Providers\Metadata\ResourceSetWrapper;
8
use POData\Common\Messages;
9
use POData\Providers\Metadata\ResourceProperty;
10
11
/**
12
 * Class ExpandedProjectionNode
13
 *
14
 * ExpandProjectionParser will create a 'Projection Tree' from the $expand
15
 * and/or $select query options, Each path segment in the $expand/$select
16
 * will be represented by a node in the projection tree, A path segment in
17
 * $expand option will be represented using this type and a path segment in
18
 * $select option (which is not appear in expand option) will be represented
19
 * using 'ProjectionNode' (base type of this type). The root of the projection
20
 * tree will be represented using the type 'RootProjectionNode' which is
21
 * derived from this type.
22
 *
23
 * @package POData\UriProcessor\QueryProcessor\ExpandProjectionParser
24
 */
25
class ExpandedProjectionNode extends ProjectionNode
26
{
27
    /**
28
     * An 'ExpandedProjectionNode' can represents either an expanded navigation
29
     * property or root of the 'Projection Tree', When the node represents
30
     * expanded navigation property this field holds reference to the resource
31
     * (entity) set pointed by the navigation property, when the node
32
     * represents 'Projection Tree' root, this fields holds reference to the
33
     * resource set that the uri resource path points to.
34
     *
35
     * @var ResourceSetWrapper
36
     */
37
    private $_resourceSetWrapper;
38
39
    private $_derivedType;
40
41
    /**
42
     * The sort information associated with the expanded navigation property or
43
     * root of 'Projection Tree', when this node represents root of the
44
     * projection tree then this member will be set if $top, $skip is specified
45
     * in the request uri or server side paging is enabled for the resource
46
     * identified by the request uri, when this node represents an expanded
47
     * navigation property then this member will be set if server side paging
48
     * is enabled for the resource set corresponding to the navigation
49
     * property.
50
     *
51
     * @var InternalOrderByInfo
52
     */
53
    private $_internalOrderByInfo;
54
55
    /**
56
     * Number of results to be skipped for this node, the value of this field
57
     * depends on the what this node actually represents,
58
     * (1) Node represents navigation property
59
     *     value will be always null
60
     * (2) Node represents root of the 'Projection Tree'
61
     *     value of the $skip query option, null if skip is absent
62
     * A null value for this filed means return all results.
63
     * @var int
64
     */
65
    private $_skipCount;
66
67
    /**
68
     * Maximum number of results to be returned for this node, the value of
69
     * this field depends on the what this node actually represents,
70
     * (1) Node represents navigation property
71
     *     The page size of the resource set pointed by the navigation
72
     *          property
73
     * (2) Node represents root of the 'Projection Tree'
74
     *     The minimum among the page size of the resource set that the
75
     *          uri resource path points to and the value of $top query option
76
     *          (if applied).
77
     * A null value for this filed means return all results.
78
     *
79
     * @var int
80
     */
81
    private $_takeCount;
82
83
    /**
84
     * The maximum number of results allowed for this node, taken from
85
     * ServiceConfiguration::_maxResultsPerCollection null means no limit
86
     * will be applied and thus all results availabe should be returned.
87
     *
88
     * @var int
89
     */
90
    private $_maxResultCount;
91
92
    /**
93
     * List of child nodes, array of ExpandedProjectionNode and/or
94
     * ProjectionNode.
95
     *
96
     * @var ProjectionNode[]
97
     */
98
    private $_childNodes = array();
99
100
    /**
101
     * When we have seen a $select path including this expanded property then
102
     * this field will be set to true, this field is used to eliminate nodes
103
     * representing segments in $expand option which are not selected.
104
     *
105
     * e.g:
106
     * $expand=A/B, A/B/C, A/B/D, X/Y, M/N & $select=A/B
107
     *     Here we need to consider only A/B, A/B/C and A/B/D, we can eliminate
108
     *     the nodes X/Y and M/N which are not selected. This field will be set
109
     *     to true for the nodes A and B.
110
     *
111
     * @var boolean
112
     */
113
    private $_selectionFound = false;
114
115
    /**
116
     * This field set to true when we have seen the special token '*', means
117
     * select all immediate (child) properties of this node.
118
     *
119
     * e.g:
120
     * $expand=A/B, A/B/C & $select=A/*
121
     *     Here we need to return only set of A with immediate properties,
122
     *     expand request for B, B/C will be ignored
123
     * $expand=A/B, A/B/C & $select=*
124
     *   Here we need to return only set pointed by uri path segment with
125
     *   immediate properties, expand request be ignored
126
     * $expand=A/B, A/B/C & $select=A/*, A/B
127
     *   Here we need to return set of A with immediate properties and
128
     *   associated B's.
129
     *
130
     * @var boolean
131
     */
132
    private $_selectAllImmediateProperties = false;
133
134
    /**
135
     * Flag which indicate whether the entire expanded subtree of this node
136
     * should be selected or not.
137
     *
138
     * e.g:
139
     * $expand=A/B, A/B/C/D & $select=A/B
140
     *     Here need to return all immediate properties of B, associated
141
     *     C with immediate properties and associated D of C with immediate
142
     *     properties, so for B, C and D this field will be true.
143
     *
144
     * @var boolean
145
     */
146
    private $_selectSubtree = false;
147
148
    /**
149
     * Constructs a new instance of node representing expanded navigation property
150
     *
151
     * @param string              $propertyName        The name of the property
152
     *                                                 to expand If this node
153
     *                                                 represents an expanded
154
     *                                                 navigation property then
155
     *                                                 this is the name of the
156
     *                                                 navigation property. if this
157
     *                                                 node represents root of the
158
     *                                                 projection tree then this
159
     *                                                 will be null.
160
     * @param ResourceProperty    $resourceProperty    The resource property for
161
     *                                                 the property to expand.
162
     *                                                 If this node represents an
163
     *                                                 expanded navigation property
164
     *                                                 then this is the resource
165
     *                                                 property of navigation
166
     *                                                 property, if this node
167
     *                                                 represents root of the
168
     *                                                 projection tree then
169
     *                                                 this will be null.
170
     * @param ResourceSetWrapper  $resourceSetWrapper  The resource set to which
171
     *                                                 the expansion leads, see the
172
     *                                                 comment of _resourceSetWrapper
173
     *                                                 field.
174
     * @param InternalOrderByInfo $internalOrderByInfo The sort information
175
     *                                                 associated with this node,
176
     *                                                 see the comments of
177
     *                                                 $_internalOrderByInfo field.
178
     * @param int                 $skipCount           The number of results to
179
     *                                                 skip, null means no
180
     *                                                 result to skip, see the
181
     *                                                 comments of _skipCount
182
     *                                                 field.
183
     * @param int                 $takeCount           The maximum number of results
184
     *                                                 to return, null means return
185
     *                                                 all available result, see the
186
     *                                                 comments of _takeCount field
187
     * @param int                 $maxResultCount      The maximum number of
188
     *                                                 expected result,see comment
189
     *                                                 of _maxResultCount field
190
     */
191
    public function __construct($propertyName, $resourceProperty,
192
        ResourceSetWrapper $resourceSetWrapper, $internalOrderByInfo,
193
        $skipCount, $takeCount, $maxResultCount, ResourceType $derivedType = null
0 ignored issues
show
Unused Code introduced by
The parameter $maxResultCount is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

193
        $skipCount, $takeCount, /** @scrutinizer ignore-unused */ $maxResultCount, ResourceType $derivedType = null

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
194
    ) {
195
        $this->_resourceSetWrapper = $resourceSetWrapper;
196
        $this->_internalOrderByInfo = $internalOrderByInfo;
197
        $this->_skipCount = $skipCount;
198
        $this->_takeCount = $takeCount;
199
        $this->_derivedType = $derivedType;
200
        parent::__construct($propertyName, $resourceProperty);
201
    }
202
203
    /**
204
     * Resource set to which the expansion represented by this node leads to
205
     * (An expansion means a set of entities associated with an entity,
206
     * associated set will be sub set of an resource set) If this node
207
     * represents an expanded navigation property, this is the resource set
208
     * to which the expanded navigation property points to, If this node is
209
     * the root of projection tree, this is the resource set that the uri
210
     * resource path points to.
211
     *
212
     * @return ResourceSetWrapper
213
     */
214
    public function getResourceSetWrapper()
215
    {
216
        return $this->_resourceSetWrapper;
217
    }
218
219
    public function getDerivedType()
220
    {
221
        return $this->_derivedType;
222
    }
223
224
    public function setDerivedType($derivedType)
225
    {
226
        return $this->_derivedType = $derivedType;
227
    }
228
229
    /**
230
     * An expansion leads by this node results in a collection of entities,
231
     * this is the resource type of these entities, This is usually the
232
     * resource type of the 'ResourceSetWrapper' for this node, but it can
233
     * also be a derived type of ResourceSetWrapper::ResourceType, this can
234
     * happen if navigation property points to a resource set but uses a
235
     * derived type.
236
     *
237
     * @return ResourceType
238
     */
239
    public function getResourceType()
240
    {
241
        return $this->resourceProperty->getResourceType();
242
    }
243
244
    /**
245
     * Gets array of child nodes
246
     *
247
     * @return ProjectionNode[]|ExpandedProjectionNode[]
248
     */
249
    public function getChildNodes()
250
    {
251
        return $this->_childNodes;
252
    }
253
254
    /**
255
     * Number of results to be skipped for this node, null means return all
256
     * results, when this node represents an expanded navigation property
257
     * then skip count will be null, If this node is the root of projection
258
     * tree, then skip count will be value of $skip query option.
259
     *
260
     * @return int
261
     */
262
    public function getSkipCount()
263
    {
264
        return $this->_skipCount;
265
    }
266
267
    /**
268
     * Maximum number of results to be returned for this node, null means
269
     * return all results, when this node represents an expanded navigation
270
     * property then take count will be page size defined for the resource
271
     * set pointed by the navigation property, If this node is the root of
272
     * projection tree then take count will be the minimum among the page
273
     * size of the the resource set that the uri resource path points to and
274
     * the value of $top query option.
275
     *
276
     * @return int
277
     */
278
    public function getTakeCount()
279
    {
280
        return $this->_takeCount;
281
    }
282
283
    /**
284
     * Gets the maximum number of expected result.
285
     *
286
     * @return int
287
     */
288
    public function getMaxResultCount()
289
    {
290
        return $this->_maxResultCount;
291
    }
292
293
    /**
294
     * Gets the sort information associated with the expanded navigation
295
     * property or root of 'Projection Tree'.
296
     *
297
     * @return InternalOrderByInfo|null
298
     */
299
    public function getInternalOrderByInfo()
300
    {
301
        return $this->_internalOrderByInfo;
302
    }
303
304
    /**
305
     * To set selection status of this node, When we have seen a $select
306
     * path segment that selects the expanded property represented by
307
     * this node then this function will be used to mark this node as selected.
308
     *
309
     * @param boolean $isSelectionFound True if selection found in this node
310
     *                                  False otherwise.
311
     *
312
     * @return void
313
     */
314
    public function setSelectionFound($isSelectionFound = true)
315
    {
316
        $this->_selectionFound = $isSelectionFound;
317
    }
318
319
    /**
320
     * To check whether this node is selected or not
321
     *
322
     * @return boolean
323
     */
324
    public function isSelectionFound()
325
    {
326
        return $this->_selectionFound;
327
    }
328
329
    /**
330
     * To set the flag indicating whether to include all immediate properties
331
     * of this node in the result or not, When we have seen a '*' in the
332
     * $select path segment, then this function will be used to set the flag
333
     * for immediate properties inclusion.
334
     *
335
     * @param boolean $selectAllImmediateProperties True if all immediate
336
     *                                              properties to be included
337
     *                                              False otherwise.
338
     *
339
     * @return void
340
     */
341
    public function setSelectAllImmediateProperties(
342
        $selectAllImmediateProperties = true
343
    ) {
344
        $this->_selectAllImmediateProperties = $selectAllImmediateProperties;
345
    }
346
347
    /**
348
     * To check whether immediate properties of the navigation property
349
     * represented by this node is to be included in the result or not.
350
     *
351
     * @return boolean
352
     */
353
    public function canSelectAllImmediateProperties()
354
    {
355
        return $this->_selectAllImmediateProperties;
356
    }
357
358
    /**
359
     * Whether all child properties of this node can be selected or not,
360
     * all child properties will be selected in 2 cases
361
     * (1) When flag for selection of all immediate properties is true
362
     *     $select=A/B/*
363
     *      Here 'immediate properties inclusion flag' will be true for B
364
     * (2) When flag for selection of this subtree is true
365
     *      $expand=A/B/D, A/B/C & $select = A/B
366
     *      Here 'subtree selection flag' will be true for B, C and D.
367
     *
368
     * @return boolean
369
     */
370
    public function canSelectAllProperties()
371
    {
372
        return $this->_selectSubtree || $this->_selectAllImmediateProperties;
373
    }
374
375
    /**
376
     * Find a child node with given name, if no such child node then
377
     * return NULL.
378
     *
379
     * @param string $propertyName Name of the property to get the
380
     *                             corresponding node.
381
     *
382
     * @return ProjectionNode|ExpandedProjectionNode|null
383
     */
384
    public function findNode($propertyName)
385
    {
386
        if (array_key_exists($propertyName, $this->_childNodes)) {
387
            return $this->_childNodes[$propertyName];
388
        }
389
390
        return null;
391
    }
392
393
    /**
394
     * To add a child node to the list of child nodes.
395
     *
396
     * @param ProjectionNode $node Node to add.
397
     *
398
     * @return void
399
     *
400
     * @throws InvalidArgumentException
401
     */
402
    public function addNode($node)
403
    {
404
        if (!($node instanceof ProjectionNode)
0 ignored issues
show
introduced by
$node is always a sub-type of POData\UriProcessor\Quer...onParser\ProjectionNode.
Loading history...
405
            && !($node instanceof ExpandedProjectionNode)
406
        ) {
407
            throw new \InvalidArgumentException(
408
                Messages::expandedProjectionNodeArgumentTypeShouldBeProjection()
409
            );
410
        }
411
412
        $this->_childNodes[$node->getPropertyName()] = $node;
413
    }
414
415
    /**
416
     * Mark the entire subtree as selected, for example
417
     * $expand=A/B/C/D/E & $select = A/B Here we need to select the entire
418
     * subtree of B i.e result should include all immedate properties of B
419
     * and associated C's, D's associated with each C and E's associated each D.
420
     *
421
     * @return void
422
     */
423
    public function markSubtreeAsSelected()
424
    {
425
        $this->_selectSubtree = true;
426
        $this->_selectAllImmediateProperties = false;
427
        foreach ($this->_childNodes as $node) {
428
            if ($node instanceof ExpandedProjectionNode) {
429
                    $node->markSubtreeAsSelected();
430
            }
431
        }
432
    }
433
434
    /**
435
     * Remove all child 'ExpandedProjectionNode's of this node which are
436
     * not selected, Recursively invoke the same function for selected
437
     * node, so that all unnecessary nodes will be removed from the subtree.
438
     *
439
     * @return void
440
     */
441
    public function removeNonSelectedNodes()
442
    {
443
        //Possilbe Node status flags are:
444
        //for $expand=A/B/C/D, X/Y
445
        // | SF | SST |
446
        // | T  | F   |  For $select=A/B, this is status of A
447
        // | T  | T   |  For $select=A/B, this is status of B
448
        // | F  | T   |  For $select=A/B, this is status of C and D
449
        // | F  | F   |  For $select=A/B, this is status of X and Y
450
        //
451
        foreach ($this->_childNodes as $propertyName => $node) {
452
            if ($node instanceof ExpandedProjectionNode) {
453
                if (!$this->_selectSubtree && !$node->_selectionFound) {
454
                    unset($this->_childNodes[$propertyName]);
455
                } else {
456
                    $node->removeNonSelectedNodes();
457
                }
458
            }
459
        }
460
    }
461
462
    /**
463
     * Remove explicity included nodes which already included implicitly, For
464
     * an expand navigation property, all immediate properties will be
465
     * implicitly selected if that navigation property is the last segment of
466
     * expand path or if there is a '*' token present after the naivgation
467
     * property, this function remove all explicity included 'ProjectionNode's
468
     * which already included implicitly.
469
     *
470
     * @return void
471
     */
472
    public function removeNodesAlreadyIncludedImplicitly()
473
    {
474
        //$select=A/B, A/B/guid, A/B/Name
475
        //Here A/B cause to implcilty include all immeiate properties of B
476
        //so remove explicitly included 'ProjectionNode' for guid and Name
477
        if ($this->_selectSubtree) {
478
            foreach ($this->_childNodes as $propertyName => $node) {
479
                if ($node instanceof ExpandedProjectionNode) {
480
                    $node->_selectSubtree = true;
481
                    $node->removeNodesAlreadyIncludedImplicitly();
482
                } else {
483
                    unset($this->_childNodes[$propertyName]);
484
                }
485
            }
486
487
            $this->_selectAllImmediateProperties = false;
488
            return;
489
        }
490
491
        //$select=A/B/*, A/B/guid, A/B/Name
492
        //Here A/B/* cause to implcitly include all immediate properties of B
493
        //so remove explicitly included 'ProjectionNode' for guid and Name
494
        foreach ($this->_childNodes as $propertyName => $node) {
495
            if ($node instanceof ExpandedProjectionNode) {
496
                $node->removeNodesAlreadyIncludedImplicitly();
497
            } else if ($this->_selectAllImmediateProperties) {
498
                unset($this->_childNodes[$propertyName]);
499
            }
500
        }
501
    }
502
503
    /**
504
     * Sort the selected nodes such that order is same as the order in which
505
     * the properties are appear in the owning type.
506
     *
507
     * @return void
508
     */
509
    public function sortNodes()
510
    {
511
        if (count($this->_childNodes) > 0) {
512
            foreach ($this->_childNodes as $childNode) {
513
                if ($childNode instanceof ExpandedProjectionNode) {
514
                        $childNode->sortNodes();
515
                }
516
            }
517
518
            //We are applying sorting in bottom-up fashion, do it only we have
519
            // more than 1 child
520
            if (count($this->_childNodes) > 1) {
521
                $existingNodes = $this->_childNodes;
522
                $this->_childNodes = array();
523
                foreach ($this->getResourceType()->getAllProperties()
524
                    as $resourceProperty) {
525
                    $propertyName = $resourceProperty->getName();
526
                    if (array_key_exists($propertyName, $existingNodes)) {
527
                        $this->_childNodes[$propertyName]
528
                            = $existingNodes[$propertyName];
529
                    }
530
                }
531
            }
532
        }
533
    }
534
}
535