Passed
Push — master ( 4ab488...bcfbc7 )
by Bálint
03:58
created

_applySelectionToProjectionTree()   C

Complexity

Conditions 17
Paths 11

Size

Total Lines 87
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 56
dl 0
loc 87
c 0
b 0
f 0
rs 5.2166
cc 17
nc 11
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace POData\UriProcessor\QueryProcessor\ExpandProjectionParser;
4
5
use POData\Common\ODataException;
6
use POData\Common\Messages;
7
use POData\Providers\Metadata\ResourceTypeKind;
8
use POData\Providers\Metadata\ResourceType;
9
use POData\Providers\Metadata\ResourceSetWrapper;
10
use POData\Providers\ProvidersWrapper;
11
use POData\Providers\Metadata\ResourcePropertyKind;
12
use POData\UriProcessor\QueryProcessor\ExpressionParser\ExpressionLexer;
13
use POData\UriProcessor\QueryProcessor\ExpressionParser\ExpressionTokenId;
14
use POData\UriProcessor\QueryProcessor\OrderByParser\OrderByParser;
15
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
16
17
/**
18
 * Class ExpandProjectionParser
19
 *
20
 * Class used to parse and validate $expand and $select query options and
21
 * create a 'Projection Tree' from these options, Syntax of the clause is:
22
 *
23
 * ExpandOrSelectPath : PathSegment [, PathSegment]
24
 * PathSegment        : SubPathSegment [\ SubPathSegment]
25
 * SubPathSegment     : DottedIdentifier
26
 * SubPathSegment     : * (Only if the SubPathSegment is last segment and
27
 *                      belongs to select path)
28
 * DottedIdentifier   : Identifier [. Identifier]
29
 * Identifier         : NavigationProperty
30
 * Identifier         : NonNavigationProperty (Only if if the SubPathSegment
31
 *                      is last segment and belongs to select path)
32
 *
33
 * @package POData\UriProcessor\QueryProcessor\ExpandProjectionParser
34
 */
35
class ExpandProjectionParser
36
{
37
    /**
38
     * The wrapper of IMetadataProvider and IQueryProvider
39
     * .
40
     * 
41
     * @var ProvidersWrapper
42
     */
43
    private $_providerWrapper;
44
45
    /**
46
     * Holds reference to the root of 'Projection Tree'
47
     * 
48
     * @var RootProjectionNode
49
     */
50
    private $_rootProjectionNode;
51
52
    /**
53
     * Creates new instance of ExpandProjectionParser
54
     * 
55
     * @param ProvidersWrapper $providerWrapper Reference to metadata and query provider wrapper
56
     * .
57
     */
58
    private function __construct(ProvidersWrapper $providerWrapper)
59
    {
60
        $this->_providerWrapper = $providerWrapper;
61
    }
62
63
    /**
64
     * Parse the given expand and select clause, validate them 
65
     * and build 'Projection Tree'
66
     * 
67
     * @param ResourceSetWrapper $resourceSetWrapper The resource set identified by the resource path uri.
68
     * @param ResourceType $resourceType The resource type of entities identified by the resource path uri.
69
     * @param InternalOrderByInfo $internalOrderInfo The top level sort information, this will be set if the $skip, $top is
70
     *                                                         specified in the 
71
     *                                                         request uri or Server 
72
     *                                                         side paging is
73
     *                                                         enabled for top level 
74
     *                                                         resource
75
     * @param int $skipCount The value of $skip option applied to the top level resource
76
     *                                                         set identified by the 
77
     *                                                         resource path uri 
78
     *                                                         null means $skip 
79
     *                                                         option is not present.
80
     * @param int $takeCount The minimum among the value of $top option applied to and
81
     *                                                         page size configured
82
     *                                                         for the top level
83
     *                                                         resource 
84
     *                                                         set identified
85
     *                                                         by the resource 
86
     *                                                         path uri.
87
     *                                                         null means $top option
88
     *                                                         is not present and/or
89
     *                                                         page size is not 
90
     *                                                         configured for top
91
     *                                                         level resource set.
92
     * @param string $expand The value of $expand clause
93
     * @param string $select The value of $select clause
94
     * @param ProvidersWrapper $providerWrapper Reference to metadata and query provider wrapper
95
     * 
96
     * @return RootProjectionNode Returns root of the 'Projection Tree'
97
     * 
98
     * @throws ODataException If any error occur while parsing expand and/or select clause
99
     *
100
     */
101
    public static function parseExpandAndSelectClause(
102
        ResourceSetWrapper $resourceSetWrapper,
103
        ResourceType $resourceType,
104
        $internalOrderInfo,
105
        $skipCount,
106
        $takeCount,
107
        $expand,
108
        $select,
109
        ProvidersWrapper $providerWrapper
110
    ) {
111
        $parser = new ExpandProjectionParser($providerWrapper);
112
        $parser->_rootProjectionNode = new RootProjectionNode(
113
            $resourceSetWrapper,
114
            $internalOrderInfo,
115
            $skipCount,
116
            $takeCount,
117
            null,
118
            $resourceType
119
        );
120
        $parser->_parseExpand($expand);
121
        $parser->_parseSelect($select);
122
        return $parser->_rootProjectionNode;
123
    }
124
125
    /**
126
     * Read the given expand clause and build 'Projection Tree', 
127
     * do nothing if the clause is null
128
     * 
129
     * @param string $expand Value of $expand clause.
130
     * 
131
     * @return void
132
     * 
133
     * @throws ODataException If any error occurs while reading expand clause
134
     *                        or building the projection tree
135
     */
136
    private function _parseExpand($expand)
137
    {
138
        if (!is_null($expand)) {
0 ignored issues
show
introduced by
The condition is_null($expand) is always false.
Loading history...
139
            $pathSegments = $this->_readExpandOrSelect($expand, false);
140
            $this->_buildProjectionTree($pathSegments);
141
            $this->_rootProjectionNode->setExpansionSpecified();
142
        }
143
    }
144
145
    /**
146
     * Read the given select clause and apply selection to the 
147
     * 'Projection Tree', mark the entire tree as selected if this
148
     * clause is null
149
     * Note: _parseExpand should to be called before the invocation 
150
     * of this function so that basic 'Projection Tree' with expand 
151
     * information will be ready.
152
     * 
153
     * @param string $select Value of $select clause.
154
     *
155
     * 
156
     * @throws ODataException If any error occurs while reading expand clause
157
     *                        or applying selection to projection tree
158
     */
159
    private function _parseSelect($select)
160
    {
161
        if (is_null($select)) {
0 ignored issues
show
introduced by
The condition is_null($select) is always false.
Loading history...
162
            $this->_rootProjectionNode->markSubtreeAsSelected();
163
        } else {
164
            $pathSegments = $this->_readExpandOrSelect($select, true);
165
            $this->_applySelectionToProjectionTree($pathSegments);
166
            $this->_rootProjectionNode->setSelectionSpecified();
167
            $this->_rootProjectionNode->removeNonSelectedNodes();
168
            $this->_rootProjectionNode->removeNodesAlreadyIncludedImplicitly();
169
            //TODO: Move sort to parseExpandAndSelectClause function
170
            $this->_rootProjectionNode->sortNodes();
171
        }
172
    }
173
174
    /**
175
     * Build 'Projection Tree' from the given expand path segments
176
     * 
177
     * @param array(array(string)) $expandPathSegments Collection of expand paths.
178
     *
179
     * 
180
     * @return void
181
     * 
182
     * @throws ODataException If any error occurs while processing the expand path segments
183
     *                        .
184
     */
185
    private function _buildProjectionTree($expandPathSegments)
186
    {
187
        foreach ($expandPathSegments as $expandSubPathSegments) {
188
            $currentNode = $this->_rootProjectionNode;
189
            foreach ($expandSubPathSegments as $expandSubPathSegment) {
190
                $resourceSetWrapper = $currentNode->getResourceSetWrapper();
0 ignored issues
show
Bug introduced by
The method getResourceSetWrapper() does not exist on POData\UriProcessor\Quer...onParser\ProjectionNode. Did you maybe mean getResourceProperty()? ( Ignorable by Annotation )

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

190
                /** @scrutinizer ignore-call */ 
191
                $resourceSetWrapper = $currentNode->getResourceSetWrapper();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
191
                $resourceType = $currentNode->getResourceType();
0 ignored issues
show
Bug introduced by
The method getResourceType() does not exist on POData\UriProcessor\Quer...onParser\ProjectionNode. Did you maybe mean getResourceProperty()? ( Ignorable by Annotation )

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

191
                /** @scrutinizer ignore-call */ 
192
                $resourceType = $currentNode->getResourceType();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
192
                $resourceProperty 
193
                    = $resourceType->resolveProperty(
194
                        $expandSubPathSegment
195
                    );
196
                if (is_null($resourceProperty)) {
197
                    throw ODataException::createSyntaxError(
198
                        Messages::expandProjectionParserPropertyNotFound(
199
                            $resourceType->getFullName(), 
200
                            $expandSubPathSegment, 
201
                            false
202
                        )
203
                    );
204
                } else if ($resourceProperty->getTypeKind() != ResourceTypeKind::ENTITY) {
205
                    throw ODataException::createBadRequestError(
206
                        Messages::expandProjectionParserExpandCanOnlyAppliedToEntity(
207
                            $resourceType->getFullName(), 
208
                            $expandSubPathSegment
209
                        )
210
                    );
211
                }
212
213
                $resourceSetWrapper = $this->_providerWrapper
214
                    ->getResourceSetWrapperForNavigationProperty(
215
                        $resourceSetWrapper, 
216
                        $resourceType, 
217
                        $resourceProperty
218
                    );
219
                if (is_null($resourceSetWrapper)) {
220
                    throw ODataException::createBadRequestError(
221
                        Messages::badRequestInvalidPropertyNameSpecified(
222
                            $resourceType->getFullName(), 
223
                            $expandSubPathSegment
224
                        )
225
                    );
226
                }
227
228
                $singleResult 
229
                    = $resourceProperty->isKindOf(
230
                        ResourcePropertyKind::RESOURCE_REFERENCE
0 ignored issues
show
Bug introduced by
POData\Providers\Metadat...ind::RESOURCE_REFERENCE of type integer is incompatible with the type POData\Providers\Metadata\ResourcePropertyKind expected by parameter $kind of POData\Providers\Metadat...rceProperty::isKindOf(). ( Ignorable by Annotation )

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

230
                        /** @scrutinizer ignore-type */ ResourcePropertyKind::RESOURCE_REFERENCE
Loading history...
231
                    );
232
                $resourceSetWrapper->checkResourceSetRightsForRead($singleResult);
233
                $pageSize = $resourceSetWrapper->getResourceSetPageSize();
234
                $internalOrderByInfo = null;
235
                if ($pageSize != 0 && !$singleResult) {
236
                    $this->_rootProjectionNode->setPagedExpandedResult(true);
237
                    $rt = $resourceSetWrapper->getResourceType();
238
                    //assert($rt != null)
239
                    $keys = array_keys($rt->getKeyProperties());
240
                    //assert(!empty($keys))
241
                    $orderBy = null;
242
                    foreach ($keys as $key) {
243
                        $orderBy = $orderBy . $key . ', ';
244
                    }
245
246
                    $orderBy = rtrim($orderBy, ', ');
247
                    $internalOrderByInfo = OrderByParser::parseOrderByClause(
248
                        $resourceSetWrapper,
249
                        $rt,
250
                        $orderBy,
251
                        $this->_providerWrapper
252
                    );
253
254
                }
255
256
                $node = $currentNode->findNode($expandSubPathSegment);
0 ignored issues
show
introduced by
The method findNode() does not exist on POData\UriProcessor\Quer...onParser\ProjectionNode. Maybe you want to declare this class abstract? ( Ignorable by Annotation )

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

256
                /** @scrutinizer ignore-call */ 
257
                $node = $currentNode->findNode($expandSubPathSegment);
Loading history...
257
                if (is_null($node)) {
258
                    $maxResultCount = $this->_providerWrapper
259
                        ->getConfiguration()->getMaxResultsPerCollection();
260
                    $node = new ExpandedProjectionNode(
261
                        $expandSubPathSegment, 
262
                        $resourceProperty, 
263
                        $resourceSetWrapper,
264
                        $internalOrderByInfo,
265
                        null, 
266
                        $pageSize == 0 ? null : $pageSize, 
267
                        $maxResultCount == PHP_INT_MAX ? null : $maxResultCount
268
                    );
269
                    $currentNode->addNode($node);
0 ignored issues
show
introduced by
The method addNode() does not exist on POData\UriProcessor\Quer...onParser\ProjectionNode. Maybe you want to declare this class abstract? ( Ignorable by Annotation )

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

269
                    $currentNode->/** @scrutinizer ignore-call */ 
270
                                  addNode($node);
Loading history...
270
                }
271
272
                $currentNode = $node;
273
            }
274
        } 
275
    }
276
277
    /**
278
     * Modify the 'Projection Tree' to include selection details
279
     * 
280
     * @param array(array(string)) $selectPathSegments Collection of select 
281
     *                                                 paths.
282
     * 
283
     * @return void
284
     * 
285
     * @throws ODataException If any error occurs while processing select
286
     *                        path segments
287
     */
288
    private function _applySelectionToProjectionTree($selectPathSegments)
289
    {
290
        foreach ($selectPathSegments as $selectSubPathSegments) {
291
            $currentNode = $this->_rootProjectionNode;
292
            $subPathCount = count($selectSubPathSegments);
293
            foreach ($selectSubPathSegments as $index => $selectSubPathSegment) {
294
                if (!($currentNode instanceof RootProjectionNode) 
295
                    && !($currentNode instanceof ExpandedProjectionNode)
296
                ) {
297
                    throw ODataException::createBadRequestError(
298
                        Messages::expandProjectionParserPropertyWithoutMatchingExpand(
299
                            $currentNode->getPropertyName()
300
                        )
301
                    );   
302
                }
303
304
                $currentNode->setSelectionFound();
305
                $isLastSegment = ($index == $subPathCount - 1);
306
                if ($selectSubPathSegment === '*') {
307
                    $currentNode->setSelectAllImmediateProperties();
308
                    break;
309
                }
310
311
                $currentResourceType = $currentNode->getResourceType();
312
                $resourceProperty 
313
                    = $currentResourceType->resolveProperty(
314
                        $selectSubPathSegment
315
                    );
316
                if (is_null($resourceProperty)) {
317
                    throw ODataException::createSyntaxError(
318
                        Messages::expandProjectionParserPropertyNotFound(
319
                            $currentResourceType->getFullName(), 
320
                            $selectSubPathSegment, 
321
                            true
322
                        )
323
                    );
324
                }
325
326
                if (!$isLastSegment) {
327
                    if ($resourceProperty->isKindOf(ResourcePropertyKind::BAG)) {
0 ignored issues
show
Bug introduced by
POData\Providers\Metadat...sourcePropertyKind::BAG of type integer is incompatible with the type POData\Providers\Metadata\ResourcePropertyKind expected by parameter $kind of POData\Providers\Metadat...rceProperty::isKindOf(). ( Ignorable by Annotation )

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

327
                    if ($resourceProperty->isKindOf(/** @scrutinizer ignore-type */ ResourcePropertyKind::BAG)) {
Loading history...
328
                        throw ODataException::createBadRequestError(
329
                            Messages::expandProjectionParserBagPropertyAsInnerSelectSegment(
330
                                $currentResourceType->getFullName(), 
331
                                $selectSubPathSegment
332
                            )
333
                        );
334
                    } else if ($resourceProperty->isKindOf(ResourcePropertyKind::PRIMITIVE)) {
335
                        throw ODataException::createBadRequestError(
336
                            Messages::expandProjectionParserPrimitivePropertyUsedAsNavigationProperty(
337
                                $currentResourceType->getFullName(), 
338
                                $selectSubPathSegment
339
                            )
340
                        );
341
                    } else if ($resourceProperty->isKindOf(ResourcePropertyKind::COMPLEX_TYPE)) {
342
                        throw ODataException::createBadRequestError(
343
                            Messages::expandProjectionParserComplexPropertyAsInnerSelectSegment(
344
                                $currentResourceType->getFullName(), 
345
                                $selectSubPathSegment
346
                            )
347
                        );
348
                    } else if ($resourceProperty->getKind() != ResourcePropertyKind::RESOURCE_REFERENCE && $resourceProperty->getKind() != ResourcePropertyKind::RESOURCESET_REFERENCE) {
349
                        throw ODataException::createInternalServerError(
350
                            Messages::expandProjectionParserUnexpectedPropertyType()
351
                        );
352
                    }
353
                }
354
355
                $node = $currentNode->findNode($selectSubPathSegment);
356
                if (is_null($node)) {
357
                    if (!$isLastSegment) {
358
                        throw ODataException::createBadRequestError(
359
                            Messages::expandProjectionParserPropertyWithoutMatchingExpand(
360
                                $selectSubPathSegment
361
                            )
362
                        );
363
                    }
364
365
                    $node = new ProjectionNode($selectSubPathSegment, $resourceProperty);
366
                    $currentNode->addNode($node);
367
                }
368
369
                $currentNode = $node;
370
                if ($currentNode instanceof ExpandedProjectionNode 
371
                    && $isLastSegment
372
                ) {
373
                    $currentNode->setSelectionFound();
374
                    $currentNode->markSubtreeAsSelected();
375
                }
376
            }
377
        }
378
    }
379
380
    /**
381
     * Read expand or select clause.
382
     * 
383
     * @param string  $value    expand or select clause to read.
384
     * @param boolean $isSelect true means $value is value of select clause
385
     *                          else value of expand clause.
386
     * 
387
     * @return array(array) An array of 'PathSegment's, each of which is array
388
     *                      of 'SubPathSegment's
389
     */
390
    private function _readExpandOrSelect($value, $isSelect)
391
    {
392
        $pathSegments = array();
393
        $lexer = new ExpressionLexer($value);
394
        $i = 0;
395
        while ($lexer->getCurrentToken()->Id != ExpressionTokenId::END) {
396
            $lastSegment = false;
397
            if ($isSelect 
398
                && $lexer->getCurrentToken()->Id == ExpressionTokenId::STAR
399
            ) {
400
                $lastSegment = true;
401
                $subPathSegment = $lexer->getCurrentToken()->Text;
402
                $lexer->nextToken();
403
            } else {
404
                $subPathSegment = $lexer->readDottedIdentifier();
405
            }
406
407
            if (!array_key_exists($i, $pathSegments)) {
408
                $pathSegments[$i] = array();
409
            }
410
411
            $pathSegments[$i][] = $subPathSegment;
412
            $tokenId = $lexer->getCurrentToken()->Id;
413
            if ($tokenId != ExpressionTokenId::END) {
414
                if ($lastSegment || $tokenId != ExpressionTokenId::SLASH) {
415
                    $lexer->validateToken(ExpressionTokenId::COMMA);
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Quer...xpressionTokenId::COMMA of type integer is incompatible with the type POData\UriProcessor\Quer...arser\ExpressionTokenId expected by parameter $tokenId of POData\UriProcessor\Quer...nLexer::validateToken(). ( Ignorable by Annotation )

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

415
                    $lexer->validateToken(/** @scrutinizer ignore-type */ ExpressionTokenId::COMMA);
Loading history...
416
                    $i++;
417
                }
418
419
                $lexer->nextToken();
420
            }
421
        }
422
423
        return $pathSegments;
424
    }
425
}