QueryProcessor   F
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 556
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 68
lcom 1
cbo 15
dl 0
loc 556
rs 2.9411
c 1
b 1
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 26 7
A process() 0 11 2
A _processQuery() 0 9 1
D _processSkipAndTop() 0 37 9
C _processOrderBy() 0 49 7
B _processFilter() 0 22 5
B _processCount() 0 45 6
B _processSkipToken() 0 35 4
C _processExpandAndSelect() 0 44 8
A _isSSPagingRequired() 0 10 2
B _readSkipOrTopOption() 0 30 4
B _checkForEmptyQueryArguments() 0 17 9
A _checkSetQueryApplicable() 0 8 2
A _checkExpandOrSelectApplicable() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like QueryProcessor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryProcessor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace POData\UriProcessor\QueryProcessor;
4
5
use POData\Providers\Metadata\Type\Int32;
6
use POData\Providers\Metadata\ResourceTypeKind;
7
use POData\UriProcessor\RequestDescription;
8
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
9
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
10
use POData\UriProcessor\QueryProcessor\SkipTokenParser\SkipTokenParser;
11
use POData\UriProcessor\QueryProcessor\OrderByParser\OrderByParser;
12
use POData\UriProcessor\QueryProcessor\ExpressionParser\ExpressionParser2;
13
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
14
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandProjectionParser;
15
use POData\Common\Messages;
16
use POData\Common\ODataException;
17
use POData\Common\ODataConstants;
18
use POData\IService;
19
use POData\Providers\Query\QueryType;
20
21
/**
22
 * Class QueryProcessor
23
 * @package POData\UriProcessor\QueryProcessor
24
 */
25
class QueryProcessor
26
{
27
    /**
28
     * Holds details of the request that client has submitted.
29
     * 
30
     * @var RequestDescription
31
     */
32
    private $request;
33
34
    /**
35
     * Holds reference to the underlying data service specific
36
     * instance.  
37
     * 
38
     * @var IService
39
     */
40
    private $service;
41
42
    /**
43
     * If $orderby, $skip, $top and $count options can be applied to the request.
44
     * @var boolean
45
     */
46
    private $_setQueryApplicable;
47
48
    /**
49
     * Whether the top level request is a candidate for paging
50
     * 
51
     * @var boolean
52
     */
53
    private $_pagingApplicable;
54
55
    /**
56
     * Whether $expand, $select can be applied to the request.
57
     * 
58
     * @var boolean
59
     */
60
    private $_expandSelectApplicable;
61
62
    /**
63
     * Creates new instance of QueryProcessor
64
     * 
65
     * @param RequestDescription $request Description of the request submitted by client.
66
     * @param IService        $service        Reference to the service implementation.
67
     */
68
    private function __construct(RequestDescription $request, IService $service ) {
69
        $this->request = $request;
70
        $this->service = $service;
71
72
        $isSingleResult = $request->isSingleResult();
73
74
	    //$top, $skip, $order, $inlinecount & $count are only applicable if:
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
75
	    //The query targets a resource collection
76
        $this->_setQueryApplicable = ($request->getTargetKind() == TargetKind::RESOURCE() && !$isSingleResult);
77
	    //Or it's a $count resource (although $inlinecount isn't applicable in this case..but there's a check somewhere else for this
78
	    $this->_setQueryApplicable |= $request->queryType == QueryType::COUNT();
79
80
	    //Paging is allowed if
81
	    //The request targets a resource collection
82
	    //and the request isn't for a $count segment
83
	    $this->_pagingApplicable = $this->request->getTargetKind() == TargetKind::RESOURCE() && !$isSingleResult && ($request->queryType != QueryType::COUNT());
84
85
	    $targetResourceType = $this->request->getTargetResourceType();
86
        $targetResourceSetWrapper = $this->request->getTargetResourceSetWrapper();
87
88
	    $this->_expandSelectApplicable = !is_null($targetResourceType)
89
            && !is_null($targetResourceSetWrapper)
90
            && $targetResourceType->getResourceTypeKind() == ResourceTypeKind::ENTITY
91
            && !$this->request->isLinkUri();
92
        
93
    }
94
95
    /**
96
     * Process the OData query options and update RequestDescription accordingly.
97
     *
98
     * @param RequestDescription $request Description of the request submitted by client.
99
     * @param IService        $service        Reference to the data service.
100
     * 
101
     * @return void
102
     * 
103
     * @throws ODataException
104
     */
105
    public static function process(RequestDescription $request, IService $service ) {
106
        $queryProcessor = new QueryProcessor($request, $service);
107
        if ($request->getTargetSource() == TargetSource::NONE) {
108
            //A service directory, metadata or batch request
109
            $queryProcessor->_checkForEmptyQueryArguments();
110
        } else {
111
            $queryProcessor->_processQuery();
112
        }
113
114
        unset($queryProcessor);
115
    }
116
117
118
119
120
121
    /**
122
     * Processes the odata query options in the request uri and update the request description instance with processed details.
123
     * @return void
124
     * 
125
     * @throws ODataException If any error occured while processing the query options.
126
     *
127
     */
128
    private function _processQuery()
129
    {
130
        $this->_processSkipAndTop();
131
        $this->_processOrderBy();
132
        $this->_processFilter();
133
        $this->_processCount();
134
        $this->_processSkipToken();
135
        $this->_processExpandAndSelect();
136
    }
137
138
    /**
139
     * Process $skip and $top options
140
     * 
141
     * @return void
142
     * 
143
     * @throws ODataException Throws syntax error if the $skip or $top option
144
     *                        is specified with non-integer value, throws
145
     *                        bad request error if the $skip or $top option
146
     *                        is not applicable for the requested resource. 
147
     */
148
    private function _processSkipAndTop()
149
    {
150
        $value = null;
151
        if ($this->_readSkipOrTopOption( ODataConstants::HTTPQUERY_STRING_SKIP, $value ) ) {
152
            $this->request->setSkipCount($value);
153
        }
154
155
        $pageSize = 0;
156
        $isPagingRequired = $this->_isSSPagingRequired();
157
        if ($isPagingRequired) {
158
            $pageSize = $this->request
159
                ->getTargetResourceSetWrapper()
160
                ->getResourceSetPageSize(); 
161
        }
162
163
        if ($this->_readSkipOrTopOption(ODataConstants::HTTPQUERY_STRING_TOP, $value) ) {
164
            $this->request->setTopOptionCount($value);
165
            if ($isPagingRequired && $pageSize < $value) {
166
                //If $top is greater than or equal to page size, 
167
                //we will need a $skiptoken and thus our response 
168
                //will be 2.0
169
                $this->request->raiseResponseVersion(2, 0);
170
                $this->request->setTopCount($pageSize);
171
            } else {
172
                $this->request->setTopCount($value);
173
            }
174
        } else if ($isPagingRequired) {
175
            $this->request->raiseResponseVersion(2, 0);
176
            $this->request->setTopCount($pageSize);
177
        }
178
179
        if (!is_null($this->request->getSkipCount())
180
            || !is_null($this->request->getTopCount())
181
        ) {
182
            $this->_checkSetQueryApplicable();
183
        }
184
    }
185
186
    /**
187
     * Process $orderby option, This function requires _processSkipAndTopOption
188
     * function to be already called as this function need to know whether 
189
     * client has requested for skip, top or paging is enabled for the 
190
     * requested resource in these cases function generates additional orderby
191
     * expression using keys.
192
     * 
193
     * @return void
194
     * 
195
     * @throws ODataException If any error occurs while parsing orderby option.
196
     */
197
    private function _processOrderBy()
198
    {
199
        $orderBy = $this->service->getHost()->getQueryStringItem( ODataConstants::HTTPQUERY_STRING_ORDERBY );
200
201
        if (!is_null($orderBy)) {
202
            $this->_checkSetQueryApplicable();
203
        }
204
205
        $targetResourceType = $this->request->getTargetResourceType();
206
        //assert($targetResourceType != null)
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
207
        /**
208
         * We need to do sorting in the folowing cases, irrespective of 
209
         * $orderby clause is present or not.
210
         * 1. If $top or $skip is specified
211
         *     skip and take will be applied on sorted list only. If $skip 
212
         *     is specified then RequestDescription::getSkipCount will give 
213
         *     non-null value. If $top is specified then 
214
         *     RequestDescription::getTopCount will give non-null value.
215
         * 2. If server side paging is enabled for the requested resource
216
         *     If server-side paging is enabled for the requested resource then 
217
         *     RequestDescription::getTopCount will give non-null value.
218
         *      
219
         */
220
        if (!is_null($this->request->getSkipCount())|| !is_null($this->request->getTopCount())) {
221
            $orderBy = !is_null($orderBy) ? $orderBy . ', ' : null;
222
            $keys = array_keys($targetResourceType->getKeyProperties());
223
            //assert(!empty($keys))
0 ignored issues
show
Unused Code Comprehensibility introduced by
88% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
224
            foreach ($keys as $key) {
225
                $orderBy = $orderBy . $key . ', ';
226
            }
227
228
            $orderBy = rtrim($orderBy, ', ');
229
        }
230
231
        if (!is_null($orderBy)) {
232
233
            $internalOrderByInfo = OrderByParser::parseOrderByClause(
234
                $this->request->getTargetResourceSetWrapper(),
0 ignored issues
show
Bug introduced by
It seems like $this->request->getTargetResourceSetWrapper() can be null; however, parseOrderByClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
235
                $targetResourceType,
0 ignored issues
show
Bug introduced by
It seems like $targetResourceType defined by $this->request->getTargetResourceType() on line 205 can be null; however, POData\UriProcessor\Quer...r::parseOrderByClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
236
                $orderBy,
237
                $this->service->getProvidersWrapper()
238
            );
239
240
            $this->request->setInternalOrderByInfo(
241
                $internalOrderByInfo
242
            );
243
244
        }
245
    }
246
247
    /**
248
     * Process the $filter option in the request and update request decription.
249
     * 
250
     * @return void
251
     * 
252
     * @throws ODataException Throws error in the following cases:
253
     *                          (1) If $filter cannot be applied to the 
254
     *                              resource targeted by the request uri
255
     *                          (2) If any error occured while parsing and
256
     *                              translating the odata $filter expression
257
     *                              to expression tree
258
     *                          (3) If any error occured while generating
259
     *                              php expression from expression tree
260
     */ 
261
    private function _processFilter()
262
    {
263
        $filter = $this->service->getHost()->getQueryStringItem( ODataConstants::HTTPQUERY_STRING_FILTER );
264
        if (is_null($filter)) {
265
            return;
266
        }
267
268
        $kind = $this->request->getTargetKind();
269
        if (!($kind == TargetKind::RESOURCE()
270
            || $kind == TargetKind::COMPLEX_OBJECT()
271
            || $this->request->queryType == QueryType::COUNT() )
272
        ) {
273
			throw ODataException::createBadRequestError(
274
                Messages::queryProcessorQueryFilterOptionNotApplicable()
275
            );
276
        }
277
        $resourceType = $this->request->getTargetResourceType();
278
        $expressionProvider = $this->service->getProvidersWrapper()->getExpressionProvider();
279
        $filterInfo = ExpressionParser2::parseExpression2($filter, $resourceType, $expressionProvider);
0 ignored issues
show
Bug introduced by
It seems like $resourceType defined by $this->request->getTargetResourceType() on line 277 can be null; however, POData\UriProcessor\Quer...er2::parseExpression2() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
280
        $this->request->setFilterInfo( $filterInfo );
281
282
    }
283
284
    /**
285
     * Process the $inlinecount option and update the request description.
286
     *
287
     * @return void
288
     * 
289
     * @throws ODataException Throws bad request error in the following cases
290
     *                          (1) If $inlinecount is disabled by the developer
291
     *                          (2) If both $count and $inlinecount specified
292
     *                          (3) If $inlinecount value is unknown
293
     *                          (4) If capability negotiation over version fails
294
     */
295
    private function _processCount()
296
    {
297
        $inlineCount = $this->service->getHost()->getQueryStringItem( ODataConstants::HTTPQUERY_STRING_INLINECOUNT );
298
299
	    //If it's not specified, we're done
300
	    if(is_null($inlineCount)) return;
301
302
	    //If the service doesn't allow count requests..then throw an exception
303
        if (!$this->service->getConfiguration()->getAcceptCountRequests()) {
304
			throw ODataException::createBadRequestError(
305
                Messages::configurationCountNotAccepted()
306
            );
307
        }
308
309
        $inlineCount = trim($inlineCount);
310
311
	    //if it's set to none, we don't do inline counts
312
        if ($inlineCount === ODataConstants::URI_ROWCOUNT_OFFOPTION) {
313
            return;
314
        }
315
316
	    //You can't specify $count & $inlinecount together
317
	    //TODO: ensure there's a test for this case see #55
318
        if ($this->request->queryType == QueryType::COUNT() ) {
319
			throw ODataException::createBadRequestError(
320
                Messages::queryProcessorInlineCountWithValueCount()
321
            );
322
        }
323
324
        $this->_checkSetQueryApplicable(); //TODO: why do we do this check?
325
326
327
        if ($inlineCount === ODataConstants::URI_ROWCOUNT_ALLOPTION) {
328
	        $this->request->queryType = QueryType::ENTITIES_WITH_COUNT();
329
330
            $this->request->raiseMinVersionRequirement( 2, 0 );
331
            $this->request->raiseResponseVersion( 2, 0 );
332
333
        } else {
334
			throw ODataException::createBadRequestError(
335
                Messages::queryProcessorInvalidInlineCountOptionError()
336
            );
337
        }
338
339
    }
340
341
    /**
342
     * Process the $skiptoken option in the request and update the request 
343
     * description, this function requires _processOrderBy method to be
344
     * already invoked.
345
     * 
346
     * @return void
347
     * 
348
     * @throws ODataException Throws bad request error in the following cases
349
     *                          (1) If $skiptoken cannot be applied to the 
350
     *                              resource targeted by the request uri
351
     *                          (2) If paging is not enabled for the resource
352
     *                              targeted by the request uri
353
     *                          (3) If parsing of $skiptoken fails
354
     *                          (4) If capability negotiation over version fails
355
     */
356
    private function _processSkipToken()
357
    {
358
        $skipToken = $this->service->getHost()->getQueryStringItem( ODataConstants::HTTPQUERY_STRING_SKIPTOKEN );
359
        if (is_null($skipToken)) {
360
            return;
361
        }
362
363
        if (!$this->_pagingApplicable) {
364
			throw ODataException::createBadRequestError(
365
                Messages::queryProcessorSkipTokenNotAllowed()
366
            );
367
        }
368
369
        if (!$this->_isSSPagingRequired()) {
370
			throw ODataException::createBadRequestError(
371
                Messages::queryProcessorSkipTokenCannotBeAppliedForNonPagedResourceSet($this->request->getTargetResourceSetWrapper())
0 ignored issues
show
Documentation introduced by
$this->request->getTargetResourceSetWrapper() is of type object<POData\Providers\...esourceSetWrapper>|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
372
            );
373
        }
374
375
        $internalOrderByInfo = $this->request->getInternalOrderByInfo();
376
        //assert($internalOrderByInfo != null)
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
377
        $targetResourceType = $this->request->getTargetResourceType();
378
        //assert($targetResourceType != null)
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
379
380
        $internalSkipTokenInfo = SkipTokenParser::parseSkipTokenClause(
381
            $targetResourceType,
0 ignored issues
show
Bug introduced by
It seems like $targetResourceType defined by $this->request->getTargetResourceType() on line 377 can be null; however, POData\UriProcessor\Quer...:parseSkipTokenClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
382
            $internalOrderByInfo,
0 ignored issues
show
Bug introduced by
It seems like $internalOrderByInfo defined by $this->request->getInternalOrderByInfo() on line 375 can be null; however, POData\UriProcessor\Quer...:parseSkipTokenClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
383
            $skipToken
384
        );
385
        $this->request->setInternalSkipTokenInfo($internalSkipTokenInfo);
386
        $this->request->raiseMinVersionRequirement( 2, 0 );
387
        $this->request->raiseResponseVersion( 2, 0 );
388
389
390
    }
391
392
    /**
393
     * Process the $expand and $select option and update the request description.
394
     * 
395
     * @return void
396
     * 
397
     * @throws ODataException Throws bad request error in the following cases
398
     *                          (1) If $expand or select cannot be applied to the
399
     *                              requested resource.
400
     *                          (2) If projection is disabled by the developer
401
     *                          (3) If some error occurs while parsing the options
402
     */
403
    private function _processExpandAndSelect()
404
    {
405
        $expand = $this->service->getHost()->getQueryStringItem( ODataConstants::HTTPQUERY_STRING_EXPAND );
406
407
        if (!is_null($expand)) {
408
            $this->_checkExpandOrSelectApplicable(ODataConstants::HTTPQUERY_STRING_EXPAND );
409
        }
410
411
        $select = $this->service->getHost()->getQueryStringItem( ODataConstants::HTTPQUERY_STRING_SELECT );
412
413
        if (!is_null($select)) {
414
            if (!$this->service->getConfiguration()->getAcceptProjectionRequests()) {
415
				throw ODataException::createBadRequestError( Messages::configurationProjectionsNotAccepted() );
416
            }
417
418
            $this->_checkExpandOrSelectApplicable( ODataConstants::HTTPQUERY_STRING_SELECT );
419
        }
420
421
        // We will generate RootProjectionNode in case of $link request also, but
422
        // expand and select in this case must be null (we are ensuring this above)
423
        // 'RootProjectionNode' is required while generating next page Link
424
        if ($this->_expandSelectApplicable || $this->request->isLinkUri() ) {
425
426
			$rootProjectionNode = ExpandProjectionParser::parseExpandAndSelectClause(
427
				 $this->request->getTargetResourceSetWrapper(),
0 ignored issues
show
Bug introduced by
It seems like $this->request->getTargetResourceSetWrapper() can be null; however, parseExpandAndSelectClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
428
				 $this->request->getTargetResourceType(),
0 ignored issues
show
Bug introduced by
It seems like $this->request->getTargetResourceType() can be null; however, parseExpandAndSelectClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
429
				 $this->request->getInternalOrderByInfo(),
0 ignored issues
show
Bug introduced by
It seems like $this->request->getInternalOrderByInfo() can be null; however, parseExpandAndSelectClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
430
				 $this->request->getSkipCount(),
431
				 $this->request->getTopCount(),
432
				 $expand,
433
				 $select,
434
				 $this->service->getProvidersWrapper()
435
			);
436
			if ($rootProjectionNode->isSelectionSpecified()) {
437
			    $this->request->raiseMinVersionRequirement(2, 0 );
438
			}
439
440
            if ($rootProjectionNode->hasPagedExpandedResult()) {
441
                $this->request->raiseResponseVersion( 2, 0 );
442
            }
443
            $this->request->setRootProjectionNode($rootProjectionNode );
444
445
        }
446
    } 
447
448
    /**
449
     * Is server side paging is configured, this function return true
450
     * if the resource targeted by the resource path is applicable
451
     * for paging and paging is enabled for the targeted resource set
452
     * else false.
453
     * 
454
     * @return boolean
455
     */
456
    private function _isSSPagingRequired()
457
    {
458
        if ($this->_pagingApplicable) {
459
            $targetResourceSetWrapper = $this->request->getTargetResourceSetWrapper();
460
            //assert($targetResourceSetWrapper != NULL)
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
461
            return ($targetResourceSetWrapper->getResourceSetPageSize() != 0);
462
        }
463
464
        return false;
465
    }
466
467
    /**
468
     * Read skip or top query option value which is expected to be positive 
469
     * integer. 
470
     * 
471
     * @param string $queryItem The name of the query item to read from request
472
     *                          uri ($skip or $top).
473
     * @param int    &$value    On return, If the requested query item is 
474
     *                          present with a valid integer value then this
475
     *                          argument will holds that integer value 
476
     *                          otherwise holds zero.
477
     * 
478
     * @return boolean True     If the requested query item with valid integer 
479
     *                          value is present in the request, false query 
480
     *                          item is absent in the request uri. 
481
     * 
482
     * @throws ODataException   Throws syntax error if the requested argument 
483
     *                          is present and it is not an integer.
484
     */
485
    private function _readSkipOrTopOption($queryItem, &$value)
486
    {
487
        $value = $this->service->getHost()->getQueryStringItem($queryItem);
488
        if (!is_null($value)) {
489
            $int = new Int32();
490
            if (!$int->validate($value, $outValue)) {
491
                throw ODataException::createSyntaxError(
492
                    Messages::queryProcessorIncorrectArgumentFormat(
493
                        $queryItem, 
494
                        $value
495
                    )
496
                );
497
            }
498
499
            $value = intval($value);
500
            if ($value < 0) {
501
                throw ODataException::createSyntaxError(
502
                    Messages::queryProcessorIncorrectArgumentFormat(
503
                        $queryItem, 
504
                        $value
505
                    )
506
                );
507
            }
508
509
            return true;
510
        }
511
512
        $value = 0;
513
        return false;
514
    }
515
 
516
    /**
517
     * Checks whether client request contains any odata query options.
518
     * 
519
     * @return void
520
     * 
521
     * @throws ODataException Throws bad request error if client request 
522
     *                        includes any odata query option.
523
     */
524
    private function _checkForEmptyQueryArguments()
525
    {
526
        $serviceHost = $this->service->getHost();
527
        if (!is_null($serviceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_FILTER))
528
            || !is_null($serviceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_EXPAND))
529
            || !is_null($serviceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_INLINECOUNT))
530
            || !is_null($serviceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_ORDERBY))
531
            || !is_null($serviceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_SELECT))
532
            || !is_null($serviceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_SKIP))
533
            || !is_null($serviceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_SKIPTOKEN))
534
            || !is_null($serviceHost->getQueryStringItem(ODataConstants::HTTPQUERY_STRING_TOP))
535
        ) {
536
			throw ODataException::createBadRequestError(
537
                Messages::queryProcessorNoQueryOptionsApplicable()
538
            );
539
        }
540
    }
541
542
    /**
543
     * To check whether the the query options $orderby, $inlinecount, $skip
544
     * or $top is applicable for the current requested resource.
545
     * 
546
     * @return void
547
     * 
548
     * @throws ODataException Throws bad request error if any of the query options $orderby, $inlinecount, $skip or $top cannot be applied to the requested resource.
549
     *
550
     */
551
    private function _checkSetQueryApplicable()
552
    {
553
        if (!$this->_setQueryApplicable) {
554
			throw ODataException::createBadRequestError(
555
                Messages::queryProcessorQuerySetOptionsNotApplicable()
556
            );
557
        }
558
    }
559
560
    /**
561
     * To check whether the the query options $select, $expand
562
     * is applicable for the current requested resource.
563
     * 
564
     * @param string $queryItem The query option to check.
565
     * 
566
     * @return void
567
     * 
568
     * @throws ODataException Throws bad request error if the query 
569
     *                        options $select, $expand cannot be 
570
     *                        applied to the requested resource. 
571
     */
572
    private function _checkExpandOrSelectApplicable($queryItem)
573
    {
574
        if (!$this->_expandSelectApplicable) {
575
			throw ODataException::createBadRequestError(
576
                Messages::queryProcessorSelectOrExpandOptionNotApplicable($queryItem)
577
            );
578
        }
579
    }
580
}