1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace POData\UriProcessor; |
4
|
|
|
|
5
|
|
|
use POData\Providers\ProvidersWrapper; |
6
|
|
|
use POData\Providers\Metadata\ResourcePropertyKind; |
7
|
|
|
use POData\Providers\Metadata\ResourceTypeKind; |
8
|
|
|
use POData\Providers\Metadata\ResourceSetWrapper; |
9
|
|
|
use POData\Providers\Metadata\ResourceProperty; |
10
|
|
|
use POData\Providers\Query\QueryType; |
11
|
|
|
use POData\UriProcessor\QueryProcessor\QueryProcessor; |
12
|
|
|
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode; |
13
|
|
|
use POData\UriProcessor\ResourcePathProcessor\ResourcePathProcessor; |
14
|
|
|
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentDescriptor; |
15
|
|
|
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind; |
16
|
|
|
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource; |
17
|
|
|
use POData\IService; |
18
|
|
|
use POData\Common\Url; |
19
|
|
|
use POData\Common\Messages; |
20
|
|
|
use POData\Common\ODataException; |
21
|
|
|
use POData\Common\InvalidOperationException; |
22
|
|
|
use POData\Common\ODataConstants; |
23
|
|
|
use POData\Providers\Query\QueryResult; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Class UriProcessor |
27
|
|
|
* |
28
|
|
|
* A type to process client's requets URI |
29
|
|
|
* The syntax of request URI is: |
30
|
|
|
* Scheme Host Port ServiceRoot ResourcePath ? QueryOption |
31
|
|
|
* For more details refer: |
32
|
|
|
* http://www.odata.org/developers/protocols/uri-conventions#UriComponents |
33
|
|
|
* |
34
|
|
|
* @package POData\UriProcessor |
35
|
|
|
*/ |
36
|
|
|
class UriProcessor |
37
|
|
|
{ |
38
|
|
|
/** |
39
|
|
|
* Description of the OData request that a client has submitted. |
40
|
|
|
* |
41
|
|
|
* @var RequestDescription |
42
|
|
|
*/ |
43
|
|
|
private $request; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Holds reference to the data service instance. |
47
|
|
|
* |
48
|
|
|
* @var IService |
49
|
|
|
*/ |
50
|
|
|
private $service; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Holds reference to the wrapper over IDSMP and IDSQP implementation. |
54
|
|
|
* |
55
|
|
|
* @var ProvidersWrapper |
56
|
|
|
*/ |
57
|
|
|
private $providers; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Collection of segment names. |
61
|
|
|
* |
62
|
|
|
* @var string[] |
63
|
|
|
*/ |
64
|
|
|
private $_segmentNames; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Collection of segment ResourceSetWrapper instances. |
68
|
|
|
* |
69
|
|
|
* @var ResourceSetWrapper[] |
70
|
|
|
*/ |
71
|
|
|
private $_segmentResourceSetWrappers; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Constructs a new instance of UriProcessor |
75
|
|
|
* |
76
|
|
|
* @param IService $service Reference to the data service instance. |
77
|
|
|
*/ |
78
|
|
|
private function __construct(IService $service) |
79
|
|
|
{ |
80
|
|
|
$this->service = $service; |
81
|
|
|
$this->providers = $service->getProvidersWrapper(); |
82
|
|
|
$this->_segmentNames = array(); |
83
|
|
|
$this->_segmentResourceSetWrappers = array(); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* Process the resource path and query options of client's request uri. |
88
|
|
|
* |
89
|
|
|
* @param IService $service Reference to the data service instance. |
90
|
|
|
* |
91
|
|
|
* @return URIProcessor |
92
|
|
|
* |
93
|
|
|
* @throws ODataException |
94
|
|
|
*/ |
95
|
|
|
public static function process(IService $service) |
96
|
|
|
{ |
97
|
|
|
$absoluteRequestUri = $service->getHost()->getAbsoluteRequestUri(); |
98
|
|
|
$absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri(); |
99
|
|
|
|
100
|
|
|
if (!$absoluteServiceUri->isBaseOf($absoluteRequestUri)) { |
101
|
|
|
throw ODataException::createInternalServerError( |
102
|
|
|
Messages::uriProcessorRequestUriDoesNotHaveTheRightBaseUri( |
103
|
|
|
$absoluteRequestUri->getUrlAsString(), |
104
|
|
|
$absoluteServiceUri->getUrlAsString() |
105
|
|
|
) |
106
|
|
|
); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
$uriProcessor = new UriProcessor($service); |
110
|
|
|
//Parse the resource path part of the request Uri. |
111
|
|
|
$uriProcessor->request = ResourcePathProcessor::process($service); |
112
|
|
|
|
113
|
|
|
$uriProcessor->request->setUriProcessor($uriProcessor); |
114
|
|
|
|
115
|
|
|
|
116
|
|
|
//Parse the query string options of the request Uri. |
117
|
|
|
QueryProcessor::process( $uriProcessor->request, $service ); |
118
|
|
|
|
119
|
|
|
return $uriProcessor; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Gets reference to the request submitted by client. |
124
|
|
|
* |
125
|
|
|
* @return RequestDescription |
126
|
|
|
*/ |
127
|
|
|
public function getRequest() |
128
|
|
|
{ |
129
|
|
|
return $this->request; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Execute the client submitted request against the data source. |
134
|
|
|
*/ |
135
|
|
|
public function execute() |
136
|
|
|
{ |
137
|
|
|
$segments = $this->request->getSegments(); |
138
|
|
|
|
139
|
|
|
foreach ($segments as $segment) { |
140
|
|
|
|
141
|
|
|
$requestTargetKind = $segment->getTargetKind(); |
142
|
|
|
|
143
|
|
|
if ($segment->getTargetSource() == TargetSource::ENTITY_SET) { |
144
|
|
|
$this->handleSegmentTargetsToResourceSet($segment); |
145
|
|
|
} else if ($requestTargetKind == TargetKind::RESOURCE()) { |
146
|
|
|
if (is_null($segment->getPrevious()->getResult())) { |
147
|
|
|
throw ODataException::createResourceNotFoundError( |
148
|
|
|
$segment->getPrevious()->getIdentifier() |
149
|
|
|
); |
150
|
|
|
} |
151
|
|
|
$this->_handleSegmentTargetsToRelatedResource($segment); |
152
|
|
|
} else if ($requestTargetKind == TargetKind::LINK()) { |
153
|
|
|
$segment->setResult($segment->getPrevious()->getResult()); |
154
|
|
|
} else if ($segment->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT) { |
155
|
|
|
// we are done, $count will the last segment and |
156
|
|
|
// taken care by _applyQueryOptions method |
157
|
|
|
$segment->setResult($this->request->getCountValue()); |
158
|
|
|
break; |
159
|
|
|
} else { |
160
|
|
|
if ($requestTargetKind == TargetKind::MEDIA_RESOURCE()) { |
161
|
|
|
if (is_null($segment->getPrevious()->getResult())) { |
162
|
|
|
throw ODataException::createResourceNotFoundError( |
163
|
|
|
$segment->getPrevious()->getIdentifier() |
164
|
|
|
); |
165
|
|
|
} |
166
|
|
|
// For MLE and Named Stream the result of last segment |
167
|
|
|
// should be that of previous segment, this is required |
168
|
|
|
// while retrieving content type or stream from IDSSP |
169
|
|
|
$segment->setResult($segment->getPrevious()->getResult()); |
170
|
|
|
// we are done, as named stream property or $value on |
171
|
|
|
// media resource will be the last segment |
172
|
|
|
break; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
$value = $segment->getPrevious()->getResult(); |
176
|
|
|
while (!is_null($segment)) { |
177
|
|
|
//TODO: what exactly is this doing here? Once a null's found it seems everything will be null |
178
|
|
|
if (!is_null($value)) { |
179
|
|
|
$value = null; |
180
|
|
|
} else { |
181
|
|
|
try { |
182
|
|
|
//see #88 |
183
|
|
|
$property = new \ReflectionProperty($value, $segment->getIdentifier()); |
184
|
|
|
$value = $property->getValue($value); |
185
|
|
|
} catch (\ReflectionException $reflectionException) { |
186
|
|
|
//throw ODataException::createInternalServerError(Messages::orderByParserFailedToAccessOrInitializeProperty($resourceProperty->getName(), $resourceType->getName())); |
|
|
|
|
187
|
|
|
} |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$segment->setResult($value); |
191
|
|
|
$segment = $segment->getNext(); |
192
|
|
|
if (!is_null($segment) && $segment->getIdentifier() == ODataConstants::URI_VALUE_SEGMENT) { |
193
|
|
|
$segment->setResult($value); |
194
|
|
|
$segment = $segment->getNext(); |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
break; |
199
|
|
|
|
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
if (is_null($segment->getNext()) || $segment->getNext()->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT) { |
203
|
|
|
$this->applyQueryOptions($segment); |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
// Apply $select and $expand options to result set, this function will be always applied |
208
|
|
|
// irrespective of return value of IDSQP2::canApplyQueryOptions which means library will |
209
|
|
|
// not delegate $expand/$select operation to IDSQP2 implementation |
210
|
|
|
$this->handleExpansion(); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Query for a resource set pointed by the given segment descriptor and update the descriptor with the result. |
215
|
|
|
* |
216
|
|
|
* @param SegmentDescriptor $segment Describes the resource set to query |
217
|
|
|
* @return void |
218
|
|
|
* |
219
|
|
|
*/ |
220
|
|
|
private function handleSegmentTargetsToResourceSet( SegmentDescriptor $segment ) { |
221
|
|
|
if ($segment->isSingleResult()) { |
222
|
|
|
$entityInstance = $this->providers->getResourceFromResourceSet( |
223
|
|
|
$segment->getTargetResourceSetWrapper(), |
224
|
|
|
$segment->getKeyDescriptor() |
225
|
|
|
); |
226
|
|
|
|
227
|
|
|
$segment->setResult($entityInstance); |
228
|
|
|
|
229
|
|
|
} else { |
230
|
|
|
|
231
|
|
|
$queryResult = $this->providers->getResourceSet( |
232
|
|
|
$this->request->queryType, |
233
|
|
|
$segment->getTargetResourceSetWrapper(), |
234
|
|
|
$this->request->getFilterInfo(), |
|
|
|
|
235
|
|
|
$this->request->getInternalOrderByInfo(), |
|
|
|
|
236
|
|
|
$this->request->getTopCount(), |
237
|
|
|
$this->request->getSkipCount() |
238
|
|
|
); |
239
|
|
|
$segment->setResult($queryResult); |
240
|
|
|
} |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Query for a related resource set or resource set reference pointed by the |
245
|
|
|
* given segment descriptor and update the descriptor with the result. |
246
|
|
|
* |
247
|
|
|
* @param SegmentDescriptor &$segment Describes the related resource |
248
|
|
|
* to query. |
249
|
|
|
* |
250
|
|
|
* @return void |
251
|
|
|
*/ |
252
|
|
|
private function _handleSegmentTargetsToRelatedResource(SegmentDescriptor $segment) { |
253
|
|
|
$projectedProperty = $segment->getProjectedProperty(); |
254
|
|
|
$projectedPropertyKind = $projectedProperty->getKind(); |
255
|
|
|
|
256
|
|
|
if ($projectedPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE) { |
257
|
|
|
if ($segment->isSingleResult()) { |
258
|
|
|
$entityInstance = $this->providers->getResourceFromRelatedResourceSet( |
259
|
|
|
$segment->getPrevious()->getTargetResourceSetWrapper(), |
260
|
|
|
$segment->getPrevious()->getResult(), |
261
|
|
|
$segment->getTargetResourceSetWrapper(), |
262
|
|
|
$projectedProperty, |
263
|
|
|
$segment->getKeyDescriptor() |
264
|
|
|
); |
265
|
|
|
|
266
|
|
|
$segment->setResult($entityInstance); |
267
|
|
|
} else { |
268
|
|
|
$queryResult = $this->providers->getRelatedResourceSet( |
269
|
|
|
$this->request->queryType, |
270
|
|
|
$segment->getPrevious()->getTargetResourceSetWrapper(), |
271
|
|
|
$segment->getPrevious()->getResult(), |
272
|
|
|
$segment->getTargetResourceSetWrapper(), |
273
|
|
|
$segment->getProjectedProperty(), |
274
|
|
|
$this->request->getFilterInfo(), |
|
|
|
|
275
|
|
|
//TODO: why are these null? see #98 |
276
|
|
|
null, // $orderby |
277
|
|
|
null, // $top |
278
|
|
|
null // $skip |
279
|
|
|
); |
280
|
|
|
|
281
|
|
|
$segment->setResult($queryResult); |
282
|
|
|
} |
283
|
|
|
} else if ($projectedPropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE) { |
284
|
|
|
$entityInstance = $this->providers->getRelatedResourceReference( |
285
|
|
|
$segment->getPrevious()->getTargetResourceSetWrapper(), |
286
|
|
|
$segment->getPrevious()->getResult(), |
287
|
|
|
$segment->getTargetResourceSetWrapper(), |
288
|
|
|
$segment->getProjectedProperty() |
289
|
|
|
); |
290
|
|
|
|
291
|
|
|
$segment->setResult($entityInstance); |
292
|
|
|
} else { |
|
|
|
|
293
|
|
|
//Unexpected state |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Applies the query options to the resource(s) retrieved from the data source. |
299
|
|
|
* |
300
|
|
|
* @param SegmentDescriptor $segment The descriptor which holds resource(s) on which query options to be applied. |
301
|
|
|
* |
302
|
|
|
*/ |
303
|
|
|
private function applyQueryOptions(SegmentDescriptor $segment) |
304
|
|
|
{ |
305
|
|
|
//TODO: I'm not really happy with this..i think i'd rather keep the result the QueryResult |
306
|
|
|
//not even bother with the setCountValue stuff (shouldn't counts be on segments?) |
307
|
|
|
//and just work with the QueryResult in the object model serializer |
308
|
|
|
$result = $segment->getResult(); |
309
|
|
|
|
310
|
|
|
if(!$result instanceof QueryResult){ |
311
|
|
|
//If the segment isn't a query result, then there's no paging or counting to be done |
312
|
|
|
return; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
|
316
|
|
|
// Note $inlinecount=allpages means include the total count regardless of paging..so we set the counts first |
317
|
|
|
// regardless if POData does the paging or not. |
318
|
|
View Code Duplication |
if ($this->request->queryType == QueryType::ENTITIES_WITH_COUNT()) { |
|
|
|
|
319
|
|
|
if ($this->providers->handlesOrderedPaging()) { |
320
|
|
|
$this->request->setCountValue($result->count); |
321
|
|
|
} else { |
322
|
|
|
$this->request->setCountValue(count($result->results)); |
323
|
|
|
} |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
//Have POData perform paging if necessary |
327
|
|
|
if(!$this->providers->handlesOrderedPaging() && !empty($result->results)){ |
328
|
|
|
$result->results = $this->performPaging($result->results); |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
//a bit surprising, but $skip and $top affects $count so update it here, not above |
332
|
|
|
//IE data.svc/Collection/$count?$top=10 returns 10 even if Collection has 11+ entries |
333
|
|
View Code Duplication |
if ($this->request->queryType == QueryType::COUNT()) { |
|
|
|
|
334
|
|
|
if ($this->providers->handlesOrderedPaging()) { |
335
|
|
|
$this->request->setCountValue($result->count); |
336
|
|
|
} else { |
337
|
|
|
$this->request->setCountValue(count($result->results)); |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
$segment->setResult($result->results); |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* If the provider does not perform the paging (ordering, top, skip) then this method does it |
346
|
|
|
* |
347
|
|
|
* @param array $result |
348
|
|
|
* @return array |
349
|
|
|
*/ |
350
|
|
|
private function performPaging(array $result) |
351
|
|
|
{ |
352
|
|
|
//Apply (implicit and explicit) $orderby option |
353
|
|
|
$internalOrderByInfo = $this->request->getInternalOrderByInfo(); |
354
|
|
|
if (!is_null($internalOrderByInfo)) { |
355
|
|
|
$orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference(); |
356
|
|
|
usort($result, $orderByFunction); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
//Apply $skiptoken option |
360
|
|
|
$internalSkipTokenInfo = $this->request->getInternalSkipTokenInfo(); |
361
|
|
|
if (!is_null($internalSkipTokenInfo)) { |
362
|
|
|
$matchingIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($result); |
363
|
|
|
$result = array_slice($result, $matchingIndex); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
//Apply $top and $skip option |
367
|
|
|
if (!empty($result)) { |
368
|
|
|
$top = $this->request->getTopCount(); |
369
|
|
|
$skip = $this->request->getSkipCount(); |
370
|
|
|
if(is_null($skip)) $skip = 0; |
371
|
|
|
|
372
|
|
|
$result = array_slice($result, $skip, $top); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
return $result; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
|
379
|
|
|
/** |
380
|
|
|
* Perform expansion. |
381
|
|
|
* |
382
|
|
|
* @return void |
383
|
|
|
*/ |
384
|
|
|
private function handleExpansion() |
385
|
|
|
{ |
386
|
|
|
$node = $this->request->getRootProjectionNode(); |
387
|
|
|
if (!is_null($node) && $node->isExpansionSpecified()) { |
388
|
|
|
$result = $this->request->getTargetResult(); |
389
|
|
|
if (!is_null($result) || is_array($result) && !empty($result)) { |
390
|
|
|
$needPop = $this->_pushSegmentForRoot(); |
391
|
|
|
$this->_executeExpansion($result); |
392
|
|
|
$this->_popSegment($needPop); |
393
|
|
|
} |
394
|
|
|
} |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Execute queries for expansion. |
399
|
|
|
* |
400
|
|
|
* @param array(mixed)/mixed $result Resource(s) whose navigation properties needs to be expanded. |
|
|
|
|
401
|
|
|
* |
402
|
|
|
* |
403
|
|
|
* @return void |
404
|
|
|
*/ |
405
|
|
|
private function _executeExpansion($result) |
406
|
|
|
{ |
407
|
|
|
$expandedProjectionNodes = $this->_getExpandedProjectionNodes(); |
408
|
|
|
foreach ($expandedProjectionNodes as $expandedProjectionNode) { |
409
|
|
|
$isCollection = $expandedProjectionNode->getResourceProperty()->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE; |
410
|
|
|
$expandedPropertyName = $expandedProjectionNode->getResourceProperty()->getName(); |
411
|
|
|
if (is_array($result)) { |
412
|
|
|
foreach ($result as $entry) { |
413
|
|
|
// Check for null entry |
414
|
|
View Code Duplication |
if ($isCollection) { |
|
|
|
|
415
|
|
|
$currentResourceSet = $this->_getCurrentResourceSetWrapper()->getResourceSet(); |
416
|
|
|
$resourceSetOfProjectedProperty = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet(); |
417
|
|
|
$projectedProperty1 = $expandedProjectionNode->getResourceProperty(); |
418
|
|
|
$result1 = $this->providers->getRelatedResourceSet( |
419
|
|
|
QueryType::ENTITIES(), //it's always entities for an expansion |
420
|
|
|
$currentResourceSet, |
421
|
|
|
$entry, |
422
|
|
|
$resourceSetOfProjectedProperty, |
423
|
|
|
$projectedProperty1, |
424
|
|
|
null, // $filter |
|
|
|
|
425
|
|
|
null, // $orderby |
426
|
|
|
null, // $top |
427
|
|
|
null // $skip |
428
|
|
|
)->results; |
429
|
|
|
if (!empty($result1)) { |
430
|
|
|
$internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo(); |
431
|
|
|
if (!is_null($internalOrderByInfo)) { |
432
|
|
|
$orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference(); |
433
|
|
|
usort($result1, $orderByFunction); |
434
|
|
|
unset($internalOrderByInfo); |
435
|
|
|
$takeCount = $expandedProjectionNode->getTakeCount(); |
436
|
|
|
if (!is_null($takeCount)) { |
437
|
|
|
$result1 = array_slice($result1, 0, $takeCount); |
438
|
|
|
} |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
$entry->$expandedPropertyName = $result1; |
442
|
|
|
$projectedProperty = $expandedProjectionNode->getResourceProperty(); |
443
|
|
|
$needPop = $this->_pushSegmentForNavigationProperty( |
444
|
|
|
$projectedProperty |
445
|
|
|
); |
446
|
|
|
$this->_executeExpansion($result1); |
447
|
|
|
$this->_popSegment($needPop); |
448
|
|
|
} else { |
449
|
|
|
$entry->$expandedPropertyName = array(); |
450
|
|
|
} |
451
|
|
|
} else { |
452
|
|
|
$currentResourceSet1 = $this->_getCurrentResourceSetWrapper()->getResourceSet(); |
453
|
|
|
$resourceSetOfProjectedProperty1 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet(); |
454
|
|
|
$projectedProperty2 = $expandedProjectionNode->getResourceProperty(); |
455
|
|
|
$result1 = $this->providers->getRelatedResourceReference( |
456
|
|
|
$currentResourceSet1, |
457
|
|
|
$entry, |
458
|
|
|
$resourceSetOfProjectedProperty1, |
459
|
|
|
$projectedProperty2 |
460
|
|
|
); |
461
|
|
|
$entry->$expandedPropertyName = $result1; |
462
|
|
|
if (!is_null($result1)) { |
463
|
|
|
$projectedProperty3 = $expandedProjectionNode->getResourceProperty(); |
464
|
|
|
$needPop = $this->_pushSegmentForNavigationProperty( |
465
|
|
|
$projectedProperty3 |
466
|
|
|
); |
467
|
|
|
$this->_executeExpansion($result1); |
468
|
|
|
$this->_popSegment($needPop); |
469
|
|
|
} |
470
|
|
|
} |
471
|
|
|
} |
472
|
|
View Code Duplication |
} else { |
|
|
|
|
473
|
|
|
if ($isCollection) { |
474
|
|
|
$currentResourceSet2 = $this->_getCurrentResourceSetWrapper()->getResourceSet(); |
475
|
|
|
$resourceSetOfProjectedProperty2 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet(); |
476
|
|
|
$projectedProperty4 = $expandedProjectionNode->getResourceProperty(); |
477
|
|
|
$result1 = $this->providers->getRelatedResourceSet( |
478
|
|
|
QueryType::ENTITIES(), //it's always entities for an expansion |
479
|
|
|
$currentResourceSet2, |
480
|
|
|
$result, |
481
|
|
|
$resourceSetOfProjectedProperty2, |
482
|
|
|
$projectedProperty4, |
483
|
|
|
null, // $filter |
|
|
|
|
484
|
|
|
null, // $orderby |
485
|
|
|
null, // $top |
486
|
|
|
null // $skip |
487
|
|
|
)->results; |
488
|
|
|
if (!empty($result1)) { |
489
|
|
|
$internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo(); |
490
|
|
|
if (!is_null($internalOrderByInfo)) { |
491
|
|
|
$orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference(); |
492
|
|
|
usort($result1, $orderByFunction); |
493
|
|
|
unset($internalOrderByInfo); |
494
|
|
|
$takeCount = $expandedProjectionNode->getTakeCount(); |
495
|
|
|
if (!is_null($takeCount)) { |
496
|
|
|
$result1 = array_slice($result1, 0, $takeCount); |
497
|
|
|
} |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
$result->$expandedPropertyName = $result1; |
501
|
|
|
$projectedProperty7 = $expandedProjectionNode->getResourceProperty(); |
502
|
|
|
$needPop = $this->_pushSegmentForNavigationProperty( |
503
|
|
|
$projectedProperty7 |
504
|
|
|
); |
505
|
|
|
$this->_executeExpansion($result1); |
506
|
|
|
$this->_popSegment($needPop); |
507
|
|
|
} else { |
508
|
|
|
$result->$expandedPropertyName = array(); |
509
|
|
|
} |
510
|
|
|
} else { |
511
|
|
|
$currentResourceSet3 = $this->_getCurrentResourceSetWrapper()->getResourceSet(); |
512
|
|
|
$resourceSetOfProjectedProperty3 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet(); |
513
|
|
|
$projectedProperty5 = $expandedProjectionNode->getResourceProperty(); |
514
|
|
|
$result1 = $this->providers->getRelatedResourceReference( |
515
|
|
|
$currentResourceSet3, |
516
|
|
|
$result, |
517
|
|
|
$resourceSetOfProjectedProperty3, |
518
|
|
|
$projectedProperty5 |
519
|
|
|
); |
520
|
|
|
$result->$expandedPropertyName = $result1; |
521
|
|
|
if (!is_null($result1)) { |
522
|
|
|
$projectedProperty6 = $expandedProjectionNode->getResourceProperty(); |
523
|
|
|
$needPop = $this->_pushSegmentForNavigationProperty( |
524
|
|
|
$projectedProperty6 |
525
|
|
|
); |
526
|
|
|
$this->_executeExpansion($result1); |
527
|
|
|
$this->_popSegment($needPop); |
528
|
|
|
} |
529
|
|
|
} |
530
|
|
|
} |
531
|
|
|
} |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* Resource set wrapper for the resource being retireved. |
536
|
|
|
* |
537
|
|
|
* @return ResourceSetWrapper |
538
|
|
|
*/ |
539
|
|
View Code Duplication |
private function _getCurrentResourceSetWrapper() |
|
|
|
|
540
|
|
|
{ |
541
|
|
|
$count = count($this->_segmentResourceSetWrappers); |
542
|
|
|
if ($count == 0) { |
543
|
|
|
return $this->request->getTargetResourceSetWrapper(); |
544
|
|
|
} else { |
545
|
|
|
return $this->_segmentResourceSetWrappers[$count - 1]; |
546
|
|
|
} |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
/** |
550
|
|
|
* Pushes a segment for the root of the tree |
551
|
|
|
* Note: Calls to this method should be balanced with calls to popSegment. |
552
|
|
|
* |
553
|
|
|
* @return bool true if the segment was pushed, false otherwise. |
554
|
|
|
*/ |
555
|
|
|
private function _pushSegmentForRoot() |
556
|
|
|
{ |
557
|
|
|
$segmentName = $this->request->getContainerName(); |
558
|
|
|
$segmentResourceSetWrapper |
559
|
|
|
= $this->request->getTargetResourceSetWrapper(); |
560
|
|
|
return $this->_pushSegment($segmentName, $segmentResourceSetWrapper); |
|
|
|
|
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
/** |
564
|
|
|
* Pushes a segment for the current navigation property being written out. |
565
|
|
|
* Note: Refer 'ObjectModelSerializerNotes.txt' for more details about |
566
|
|
|
* 'Segment Stack' and this method. |
567
|
|
|
* Note: Calls to this method should be balanced with calls to popSegment. |
568
|
|
|
* |
569
|
|
|
* @param ResourceProperty &$resourceProperty Current navigation property |
570
|
|
|
* being written out |
571
|
|
|
* |
572
|
|
|
* @return bool true if a segment was pushed, false otherwise |
573
|
|
|
* |
574
|
|
|
* @throws InvalidOperationException If this function invoked with non-navigation |
575
|
|
|
* property instance. |
576
|
|
|
*/ |
577
|
|
View Code Duplication |
private function _pushSegmentForNavigationProperty(ResourceProperty &$resourceProperty) |
|
|
|
|
578
|
|
|
{ |
579
|
|
|
if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY) { |
580
|
|
|
$this->assert( |
581
|
|
|
!empty($this->_segmentNames), |
582
|
|
|
'!is_empty($this->_segmentNames' |
583
|
|
|
); |
584
|
|
|
$currentResourceSetWrapper = $this->_getCurrentResourceSetWrapper(); |
585
|
|
|
$currentResourceType = $currentResourceSetWrapper->getResourceType(); |
586
|
|
|
$currentResourceSetWrapper = $this->service |
587
|
|
|
->getProvidersWrapper() |
588
|
|
|
->getResourceSetWrapperForNavigationProperty( |
589
|
|
|
$currentResourceSetWrapper, |
|
|
|
|
590
|
|
|
$currentResourceType, |
591
|
|
|
$resourceProperty |
592
|
|
|
); |
593
|
|
|
|
594
|
|
|
$this->assert( |
595
|
|
|
!is_null($currentResourceSetWrapper), |
596
|
|
|
'!null($currentResourceSetWrapper)' |
597
|
|
|
); |
598
|
|
|
return $this->_pushSegment( |
599
|
|
|
$resourceProperty->getName(), |
600
|
|
|
$currentResourceSetWrapper |
|
|
|
|
601
|
|
|
); |
602
|
|
|
} else { |
603
|
|
|
throw new InvalidOperationException( |
604
|
|
|
'pushSegmentForNavigationProperty should not be called with non-entity type' |
605
|
|
|
); |
606
|
|
|
} |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
/** |
610
|
|
|
* Gets collection of expanded projection nodes under the current node. |
611
|
|
|
* |
612
|
|
|
* @return ExpandedProjectionNode[] List of nodes describing expansions for the current segment |
613
|
|
|
* |
614
|
|
|
*/ |
615
|
|
|
private function _getExpandedProjectionNodes() |
616
|
|
|
{ |
617
|
|
|
$expandedProjectionNode = $this->_getCurrentExpandedProjectionNode(); |
618
|
|
|
$expandedProjectionNodes = array(); |
619
|
|
|
if (!is_null($expandedProjectionNode)) { |
620
|
|
|
foreach ($expandedProjectionNode->getChildNodes() as $node) { |
621
|
|
|
if ($node instanceof ExpandedProjectionNode) { |
622
|
|
|
$expandedProjectionNodes[] = $node; |
623
|
|
|
} |
624
|
|
|
} |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
return $expandedProjectionNodes; |
628
|
|
|
} |
629
|
|
|
|
630
|
|
|
/** |
631
|
|
|
* Find a 'ExpandedProjectionNode' instance in the projection tree |
632
|
|
|
* which describes the current segment. |
633
|
|
|
* |
634
|
|
|
* @return ExpandedProjectionNode|null |
635
|
|
|
*/ |
636
|
|
View Code Duplication |
private function _getCurrentExpandedProjectionNode() |
|
|
|
|
637
|
|
|
{ |
638
|
|
|
$expandedProjectionNode |
639
|
|
|
= $this->request->getRootProjectionNode(); |
640
|
|
|
if (!is_null($expandedProjectionNode)) { |
641
|
|
|
$depth = count($this->_segmentNames); |
642
|
|
|
if ($depth != 0) { |
643
|
|
|
for ($i = 1; $i < $depth; $i++) { |
644
|
|
|
$expandedProjectionNode |
645
|
|
|
= $expandedProjectionNode->findNode($this->_segmentNames[$i]); |
646
|
|
|
$this->assert( |
647
|
|
|
!is_null($expandedProjectionNode), |
648
|
|
|
'!is_null($expandedProjectionNode)' |
649
|
|
|
); |
650
|
|
|
$this->assert( |
651
|
|
|
$expandedProjectionNode instanceof ExpandedProjectionNode, |
652
|
|
|
'$expandedProjectionNode instanceof ExpandedProjectionNode' |
653
|
|
|
); |
654
|
|
|
} |
655
|
|
|
} |
656
|
|
|
} |
657
|
|
|
|
658
|
|
|
return $expandedProjectionNode; |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
/** |
662
|
|
|
* Pushes information about the segment whose instance is going to be |
663
|
|
|
* retrieved from the IDSQP implementation |
664
|
|
|
* Note: Calls to this method should be balanced with calls to popSegment. |
665
|
|
|
* |
666
|
|
|
* @param string $segmentName Name of segment to push. |
667
|
|
|
* @param ResourceSetWrapper &$resourceSetWrapper The resource set wrapper |
668
|
|
|
* to push. |
669
|
|
|
* |
670
|
|
|
* @return bool true if the segment was push, false otherwise |
671
|
|
|
*/ |
672
|
|
View Code Duplication |
private function _pushSegment($segmentName, ResourceSetWrapper &$resourceSetWrapper) |
|
|
|
|
673
|
|
|
{ |
674
|
|
|
$rootProjectionNode = $this->request->getRootProjectionNode(); |
675
|
|
|
if (!is_null($rootProjectionNode) |
676
|
|
|
&& $rootProjectionNode->isExpansionSpecified() |
677
|
|
|
) { |
678
|
|
|
array_push($this->_segmentNames, $segmentName); |
679
|
|
|
array_push($this->_segmentResourceSetWrappers, $resourceSetWrapper); |
680
|
|
|
return true; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
return false; |
684
|
|
|
} |
685
|
|
|
|
686
|
|
|
/** |
687
|
|
|
* Pops segment information from the 'Segment Stack' |
688
|
|
|
* Note: Calls to this method should be balanced with previous calls |
689
|
|
|
* to _pushSegment. |
690
|
|
|
* |
691
|
|
|
* @param boolean $needPop Is a pop required. Only true if last push |
692
|
|
|
* was successful. |
693
|
|
|
* |
694
|
|
|
* @return void |
695
|
|
|
* |
696
|
|
|
* @throws InvalidOperationException If found un-balanced call |
697
|
|
|
* with _pushSegment |
698
|
|
|
*/ |
699
|
|
View Code Duplication |
private function _popSegment($needPop) |
|
|
|
|
700
|
|
|
{ |
701
|
|
|
if ($needPop) { |
702
|
|
|
if (!empty($this->_segmentNames)) { |
703
|
|
|
array_pop($this->_segmentNames); |
704
|
|
|
array_pop($this->_segmentResourceSetWrappers); |
705
|
|
|
} else { |
706
|
|
|
throw new InvalidOperationException( |
707
|
|
|
'Found non-balanced call to _pushSegment and popSegment' |
708
|
|
|
); |
709
|
|
|
} |
710
|
|
|
} |
711
|
|
|
} |
712
|
|
|
|
713
|
|
|
/** |
714
|
|
|
* Assert that the given condition is true. |
715
|
|
|
* |
716
|
|
|
* @param boolean $condition Constion to assert. |
717
|
|
|
* @param string $conditionAsString Message to show incase assertion fails. |
718
|
|
|
* |
719
|
|
|
* @return void |
720
|
|
|
* |
721
|
|
|
* @throws InvalidOperationException |
722
|
|
|
*/ |
723
|
|
|
protected function assert($condition, $conditionAsString) |
724
|
|
|
{ |
725
|
|
|
if (!$condition) { |
726
|
|
|
throw new InvalidOperationException( |
727
|
|
|
"Unexpected state, expecting $conditionAsString" |
728
|
|
|
); |
729
|
|
|
} |
730
|
|
|
} |
731
|
|
|
} |
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.