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

parseExpandAndSelectClause()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 22
c 0
b 0
f 0
rs 9.9
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
}