Passed
Branch master (950424)
by Christopher
11:06
created

ExpandedProjectionNode::getSkipCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace POData\UriProcessor\QueryProcessor\ExpandProjectionParser;
4
5
use InvalidArgumentException;
6
use POData\Common\Messages;
7
use POData\Providers\Metadata\ResourceProperty;
8
use POData\Providers\Metadata\ResourceSetWrapper;
9
use POData\Providers\Metadata\ResourceType;
10
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
11
12
/**
13
 * Class ExpandedProjectionNode.
14
 *
15
 * ExpandProjectionParser will create a 'Projection Tree' from the $expand
16
 * and/or $select query options, Each path segment in the $expand/$select
17
 * will be represented by a node in the projection tree, A path segment in
18
 * $expand option will be represented using this type and a path segment in
19
 * $select option (which is not appear in expand option) will be represented
20
 * using 'ProjectionNode' (base type of this type). The root of the projection
21
 * tree will be represented using the type 'RootProjectionNode' which is
22
 * derived from this type.
23
 */
24
class ExpandedProjectionNode extends ProjectionNode
25
{
26
    /**
27
     * An 'ExpandedProjectionNode' can represents either an expanded navigation
28
     * property or root of the 'Projection Tree', When the node represents
29
     * expanded navigation property this field holds reference to the resource
30
     * (entity) set pointed by the navigation property, when the node
31
     * represents 'Projection Tree' root, this fields holds reference to the
32
     * resource set that the uri resource path points to.
33
     *
34
     * @var ResourceSetWrapper
35
     */
36
    private $resourceSetWrapper;
37
38
    /**
39
     * The sort information associated with the expanded navigation property or
40
     * root of 'Projection Tree', when this node represents root of the
41
     * projection tree then this member will be set if $top, $skip is specified
42
     * in the request uri or server side paging is enabled for the resource
43
     * identified by the request uri, when this node represents an expanded
44
     * navigation property then this member will be set if server side paging
45
     * is enabled for the resource set corresponding to the navigation
46
     * property.
47
     *
48
     * @var InternalOrderByInfo
49
     */
50
    private $internalOrderByInfo;
51
52
    /**
53
     * Number of results to be skipped for this node, the value of this field
54
     * depends on the what this node actually represents,
55
     * (1) Node represents navigation property
56
     *     value will be always null
57
     * (2) Node represents root of the 'Projection Tree'
58
     *     value of the $skip query option, null if skip is absent
59
     * A null value for this filed means return all results.
60
     *
61
     * @var int
62
     */
63
    private $skipCount;
64
65
    /**
66
     * Maximum number of results to be returned for this node, the value of
67
     * this field depends on the what this node actually represents,
68
     * (1) Node represents navigation property
69
     *     The page size of the resource set pointed by the navigation
70
     *          property
71
     * (2) Node represents root of the 'Projection Tree'
72
     *     The minimum among the page size of the resource set that the
73
     *          uri resource path points to and the value of $top query option
74
     *          (if applied).
75
     * A null value for this filed means return all results.
76
     *
77
     * @var int
78
     */
79
    private $takeCount;
80
81
    /**
82
     * The maximum number of results allowed for this node, taken from
83
     * ServiceConfiguration::_maxResultsPerCollection null means no limit
84
     * will be applied and thus all results available should be returned.
85
     *
86
     * @var int
87
     */
88
    private $maxResultCount;
89
90
    /**
91
     * List of child nodes, array of ExpandedProjectionNode and/or
92
     * ProjectionNode.
93
     *
94
     * @var ProjectionNode[]
95
     */
96
    private $childNodes = [];
97
98
    /**
99
     * When we have seen a $select path including this expanded property then
100
     * this field will be set to true, this field is used to eliminate nodes
101
     * representing segments in $expand option which are not selected.
102
     *
103
     * e.g:
104
     * $expand=A/B, A/B/C, A/B/D, X/Y, M/N & $select=A/B
105
     *     Here we need to consider only A/B, A/B/C and A/B/D, we can eliminate
106
     *     the nodes X/Y and M/N which are not selected. This field will be set
107
     *     to true for the nodes A and B.
108
     *
109
     * @var bool
110
     */
111
    private $selectionFound = false;
112
113
    /**
114
     * This field set to true when we have seen the special token '*', means
115
     * select all immediate (child) properties of this node.
116
     *
117
     * e.g:
118
     * $expand=A/B, A/B/C & $select=A/*
119
     *     Here we need to return only set of A with immediate properties,
120
     *     expand request for B, B/C will be ignored
121
     * $expand=A/B, A/B/C & $select=*
122
     *   Here we need to return only set pointed by uri path segment with
123
     *   immediate properties, expand request be ignored
124
     * $expand=A/B, A/B/C & $select=A/*, A/B
125
     *   Here we need to return set of A with immediate properties and
126
     *   associated B's.
127
     *
128
     * @var bool
129
     */
130
    private $selectAllImmediateProperties = false;
131
132
    /**
133
     * Flag which indicate whether the entire expanded subtree of this node
134
     * should be selected or not.
135
     *
136
     * e.g:
137
     * $expand=A/B, A/B/C/D & $select=A/B
138
     *     Here need to return all immediate properties of B, associated
139
     *     C with immediate properties and associated D of C with immediate
140
     *     properties, so for B, C and D this field will be true.
141
     *
142
     * @var bool
143
     */
144
    private $selectSubtree = false;
145
146
    /**
147
     * Constructs a new instance of node representing expanded navigation property.
148
     *
149
     * @param string|null           $propertyName        The name of the property
150
     *                                                   to expand. If this node
151
     *                                                   represents an expanded
152
     *                                                   navigation property then
153
     *                                                   this is the name of the
154
     *                                                   navigation property. If this
155
     *                                                   node represents root of the
156
     *                                                   projection tree then this
157
     *                                                   will be null
158
     * @param ResourceSetWrapper    $resourceSetWrapper  The resource set to which
159
     *                                                   the expansion leads, see the
160
     *                                                   comment of _resourceSetWrapper
161
     *                                                   field
162
     * @param InternalOrderByInfo   $internalOrderByInfo The sort information
163
     *                                                   associated with this node,
164
     *                                                   see the comments of
165
     *                                                   $_internalOrderByInfo field
166
     * @param int|null              $skipCount           The number of results to
167
     *                                                   skip, null means no
168
     *                                                   result to skip, see the
169
     *                                                   comments of _skipCount
170
     *                                                   field
171
     * @param int                   $takeCount           The maximum number of results
172
     *                                                   to return, null means return
173
     *                                                   all available result, see the
174
     *                                                   comments of _takeCount field
175
     * @param int                   $maxResultCount      The maximum number of
176
     *                                                   expected results,see comment
177
     *                                                   of _maxResultCount field
178
     * @param ResourceProperty|null $resourceProperty    The resource property for
179
     *                                                   the property to expand.
180
     *                                                   If this node represents an
181
     *                                                   expanded navigation property
182
     *                                                   then this is the resource
183
     *                                                   property of navigation
184
     *                                                   property, if this node
185
     *                                                   represents root of the
186
     *                                                   projection tree then
187
     *                                                   this will be null
188
     */
189
    public function __construct(
190
        $propertyName,
191
        ResourceSetWrapper $resourceSetWrapper,
192
        $internalOrderByInfo,
193
        $skipCount,
194
        $takeCount,
195
        $maxResultCount,
196
        ResourceProperty $resourceProperty = null
197
    ) {
198
        $this->resourceSetWrapper = $resourceSetWrapper;
199
        $this->internalOrderByInfo = $internalOrderByInfo;
200
        $this->skipCount = $skipCount;
201
        $this->takeCount = $takeCount;
202
        $this->maxResultCount = $maxResultCount;
203
        parent::__construct($propertyName, $resourceProperty);
204
    }
205
206
    /**
207
     * Resource set to which the expansion represented by this node leads to
208
     * (An expansion means a set of entities associated with an entity,
209
     * associated set will be sub set of an resource set) If this node
210
     * represents an expanded navigation property, this is the resource set
211
     * to which the expanded navigation property points to, If this node is
212
     * the root of projection tree, this is the resource set that the uri
213
     * resource path points to.
214
     *
215
     * @return ResourceSetWrapper
216
     */
217
    public function getResourceSetWrapper()
218
    {
219
        return $this->resourceSetWrapper;
220
    }
221
222
    /**
223
     * An expansion leads by this node results in a collection of entities,
224
     * this is the resource type of these entities, This is usually the
225
     * resource type of the 'ResourceSetWrapper' for this node, but it can
226
     * also be a derived type of ResourceSetWrapper::ResourceType, this can
227
     * happen if navigation property points to a resource set but uses a
228
     * derived type.
229
     *
230
     * @return ResourceType
231
     */
232
    public function getResourceType()
233
    {
234
        return $this->resourceProperty->getResourceType();
235
    }
236
237
    /**
238
     * Gets array of child nodes.
239
     *
240
     * @return ProjectionNode[]|ExpandedProjectionNode[]
241
     */
242
    public function getChildNodes()
243
    {
244
        return $this->childNodes;
245
    }
246
247
    /**
248
     * Number of results to be skipped for this node, null means return all
249
     * results, when this node represents an expanded navigation property
250
     * then skip count will be null, If this node is the root of projection
251
     * tree, then skip count will be value of $skip query option.
252
     *
253
     * @return int
254
     */
255
    public function getSkipCount()
256
    {
257
        return $this->skipCount;
258
    }
259
260
    /**
261
     * Maximum number of results to be returned for this node, null means
262
     * return all results, when this node represents an expanded navigation
263
     * property then take count will be page size defined for the resource
264
     * set pointed by the navigation property, If this node is the root of
265
     * projection tree then take count will be the minimum among the page
266
     * size of the the resource set that the uri resource path points to and
267
     * the value of $top query option.
268
     *
269
     * @return int
270
     */
271
    public function getTakeCount()
272
    {
273
        return $this->takeCount;
274
    }
275
276
    /**
277
     * Gets the maximum number of expected result.
278
     *
279
     * @return int
280
     */
281
    public function getMaxResultCount()
282
    {
283
        return $this->maxResultCount;
284
    }
285
286
    /**
287
     * Gets the sort information associated with the expanded navigation
288
     * property or root of 'Projection Tree'.
289
     *
290
     * @return InternalOrderByInfo|null
291
     */
292
    public function getInternalOrderByInfo()
293
    {
294
        return $this->internalOrderByInfo;
295
    }
296
297
    /**
298
     * To set selection status of this node, When we have seen a $select
299
     * path segment that selects the expanded property represented by
300
     * this node then this function will be used to mark this node as selected.
301
     *
302
     * @param  bool $isSelectionFound True if selection found in this node
303
     *                                False otherwise
304
     * @return void
305
     */
306
    public function setSelectionFound($isSelectionFound = true)
307
    {
308
        $this->selectionFound = boolval($isSelectionFound);
309
    }
310
311
    /**
312
     * To check whether this node is selected or not.
313
     *
314
     * @return bool
315
     */
316
    public function isSelectionFound()
317
    {
318
        return $this->selectionFound;
319
    }
320
321
    /**
322
     * To set the flag indicating whether to include all immediate properties
323
     * of this node in the result or not, When we have seen a '*' in the
324
     * $select path segment, then this function will be used to set the flag
325
     * for immediate properties inclusion.
326
     *
327
     * @param  bool $selectAllImmediateProperties True if all immediate
328
     *                                            properties to be included
329
     *                                            False otherwise
330
     * @return void
331
     */
332
    public function setSelectAllImmediateProperties(
333
        $selectAllImmediateProperties = true
334
    ) {
335
        $this->selectAllImmediateProperties = $selectAllImmediateProperties;
336
    }
337
338
    /**
339
     * To check whether immediate properties of the navigation property
340
     * represented by this node is to be included in the result or not.
341
     *
342
     * @return bool
343
     */
344
    public function canSelectAllImmediateProperties()
345
    {
346
        return $this->selectAllImmediateProperties;
347
    }
348
349
    /**
350
     * Whether all child properties of this node can be selected or not,
351
     * all child properties will be selected in 2 cases
352
     * (1) When flag for selection of all immediate properties is true
353
     *     $select=A/B/*
354
     *      Here 'immediate properties inclusion flag' will be true for B
355
     * (2) When flag for selection of this subtree is true
356
     *      $expand=A/B/D, A/B/C & $select = A/B
357
     *      Here 'subtree selection flag' will be true for B, C and D.
358
     *
359
     * @return bool
360
     */
361
    public function canSelectAllProperties()
362
    {
363
        return $this->selectSubtree || $this->selectAllImmediateProperties;
364
    }
365
366
    /**
367
     * Find a child node with given name, if no such child node then
368
     * return NULL.
369
     *
370
     * @param string $propertyName Name of the property to get the
371
     *                             corresponding node
372
     *
373
     * @return ProjectionNode|ExpandedProjectionNode|null
374
     */
375
    public function findNode($propertyName)
376
    {
377
        if (array_key_exists($propertyName, $this->childNodes)) {
378
            return $this->childNodes[$propertyName];
379
        }
380
        return null;
381
    }
382
383
    /**
384
     * To add a child node to the list of child nodes.
385
     *
386
     * @param ProjectionNode $node Node to add
387
     *
388
     * @return void
389
     */
390
    public function addNode(ProjectionNode $node)
391
    {
392
        $this->childNodes[$node->getPropertyName()] = $node;
393
    }
394
395
    /**
396
     * Mark the entire subtree as selected, for example
397
     * $expand=A/B/C/D/E & $select = A/B Here we need to select the entire
398
     * subtree of B i.e result should include all immediate properties of B
399
     * and associated C's, D's associated with each C and E's associated each D.
400
     *
401
     * @return void
402
     */
403
    public function markSubtreeAsSelected()
404
    {
405
        $this->selectSubtree = true;
406
        $this->selectAllImmediateProperties = false;
407
        foreach ($this->childNodes as $node) {
408
            if ($node instanceof self) {
409
                $node->markSubtreeAsSelected();
410
            }
411
        }
412
    }
413
414
    /**
415
     * Remove all child 'ExpandedProjectionNode's of this node which are
416
     * not selected, Recursively invoke the same function for selected
417
     * node, so that all unnecessary nodes will be removed from the subtree.
418
     *
419
     * @return void
420
     */
421
    public function removeNonSelectedNodes()
422
    {
423
        //Possible Node status flags are:
424
        //for $expand=A/B/C/D, X/Y
425
        // | SF | SST |
426
        // | T  | F   |  For $select=A/B, this is status of A
427
        // | T  | T   |  For $select=A/B, this is status of B
428
        // | F  | T   |  For $select=A/B, this is status of C and D
429
        // | F  | F   |  For $select=A/B, this is status of X and Y
430
431
        foreach ($this->childNodes as $propertyName => $node) {
432
            if ($node instanceof self) {
433
                if (!$this->selectSubtree && !$node->selectionFound) {
434
                    unset($this->childNodes[$propertyName]);
435
                } else {
436
                    $node->removeNonSelectedNodes();
437
                }
438
            }
439
        }
440
    }
441
442
    /**
443
     * Remove explicitly included nodes which already included implicitly, For
444
     * an expand navigation property, all immediate properties will be
445
     * implicitly selected if that navigation property is the last segment of
446
     * expand path or if there is a '*' token present after the navigation
447
     * property, this function remove all explicitly included 'ProjectionNode's
448
     * which already included implicitly.
449
     *
450
     * @return void
451
     */
452
    public function removeNodesAlreadyIncludedImplicitly()
453
    {
454
        //$select=A/B, A/B/guid, A/B/Name
455
        //Here A/B cause to implicitly include all immediate properties of B
456
        //so remove explicitly included 'ProjectionNode' for guid and Name
457
        if ($this->selectSubtree) {
458
            foreach ($this->childNodes as $propertyName => $node) {
459
                if ($node instanceof self) {
460
                    $node->selectSubtree = true;
461
                    $node->removeNodesAlreadyIncludedImplicitly();
462
                } else {
463
                    unset($this->childNodes[$propertyName]);
464
                }
465
            }
466
467
            $this->selectAllImmediateProperties = false;
468
469
            return;
470
        }
471
472
        //$select=A/B/*, A/B/guid, A/B/Name
473
        //Here A/B/* cause to implicitly include all immediate properties of B
474
        //so remove explicitly included 'ProjectionNode' for guid and Name
475
        foreach ($this->childNodes as $propertyName => $node) {
476
            if ($node instanceof self) {
477
                $node->removeNodesAlreadyIncludedImplicitly();
478
            } elseif ($this->selectAllImmediateProperties) {
479
                unset($this->childNodes[$propertyName]);
480
            }
481
        }
482
    }
483
484
    /**
485
     * Sort the selected nodes such that order is same as the order in which
486
     * the properties are appear in the owning type.
487
     *
488
     * @return void
489
     */
490
    public function sortNodes()
491
    {
492
        if (count($this->childNodes) > 0) {
493
            foreach ($this->childNodes as $childNode) {
494
                if ($childNode instanceof self) {
495
                    $childNode->sortNodes();
496
                }
497
            }
498
499
            //We are applying sorting in bottom-up fashion, do it only we have
500
            // more than 1 child
501
            if (count($this->childNodes) > 1) {
502
                $existingNodes = $this->childNodes;
503
                $this->childNodes = [];
504
                foreach ($this->getResourceType()->getAllProperties() as $resourceProperty) {
505
                    $propertyName = $resourceProperty->getName();
506
                    if (array_key_exists($propertyName, $existingNodes)) {
507
                        $this->childNodes[$propertyName] = $existingNodes[$propertyName];
508
                    }
509
                }
510
            }
511
        }
512
    }
513
}
514