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: |
|
|
|
|
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) |
|
|
|
|
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)) |
|
|
|
|
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(), |
|
|
|
|
235
|
|
|
$targetResourceType, |
|
|
|
|
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); |
|
|
|
|
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()) |
|
|
|
|
372
|
|
|
); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
$internalOrderByInfo = $this->request->getInternalOrderByInfo(); |
376
|
|
|
//assert($internalOrderByInfo != null) |
|
|
|
|
377
|
|
|
$targetResourceType = $this->request->getTargetResourceType(); |
378
|
|
|
//assert($targetResourceType != null) |
|
|
|
|
379
|
|
|
|
380
|
|
|
$internalSkipTokenInfo = SkipTokenParser::parseSkipTokenClause( |
381
|
|
|
$targetResourceType, |
|
|
|
|
382
|
|
|
$internalOrderByInfo, |
|
|
|
|
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(), |
|
|
|
|
428
|
|
|
$this->request->getTargetResourceType(), |
|
|
|
|
429
|
|
|
$this->request->getInternalOrderByInfo(), |
|
|
|
|
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) |
|
|
|
|
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
|
|
|
} |
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.