|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace AlgoWeb\PODataLaravel\Query; |
|
4
|
|
|
|
|
5
|
|
|
use AlgoWeb\PODataLaravel\Auth\NullAuthProvider; |
|
6
|
|
|
use AlgoWeb\PODataLaravel\Enums\ActionVerb; |
|
7
|
|
|
use AlgoWeb\PODataLaravel\Interfaces\AuthInterface; |
|
8
|
|
|
use AlgoWeb\PODataLaravel\Models\MetadataTrait; |
|
9
|
|
|
use Illuminate\Database\Eloquent\Builder; |
|
10
|
|
|
use Illuminate\Database\Eloquent\Collection; |
|
11
|
|
|
use Illuminate\Database\Eloquent\Model; |
|
12
|
|
|
use Illuminate\Database\Eloquent\Relations\Relation; |
|
13
|
|
|
use Illuminate\Support\Facades\App; |
|
14
|
|
|
use POData\Common\InvalidOperationException; |
|
15
|
|
|
use POData\Common\ODataException; |
|
16
|
|
|
use POData\Providers\Metadata\ResourceProperty; |
|
17
|
|
|
use POData\Providers\Metadata\ResourceSet; |
|
18
|
|
|
use POData\Providers\Query\QueryResult; |
|
19
|
|
|
use POData\Providers\Query\QueryType; |
|
20
|
|
|
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo; |
|
21
|
|
|
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo; |
|
22
|
|
|
use POData\UriProcessor\QueryProcessor\SkipTokenParser\SkipTokenInfo; |
|
23
|
|
|
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor; |
|
24
|
|
|
use Symfony\Component\Process\Exception\InvalidArgumentException; |
|
25
|
|
|
|
|
26
|
|
|
class LaravelReadQuery extends LaravelBaseQuery |
|
27
|
|
|
{ |
|
28
|
|
|
/** |
|
29
|
|
|
* Gets collection of entities belongs to an entity set |
|
30
|
|
|
* IE: http://host/EntitySet |
|
31
|
|
|
* http://host/EntitySet?$skip=10&$top=5&filter=Prop gt Value. |
|
32
|
|
|
* |
|
33
|
|
|
* @param QueryType $queryType Is this is a query for a count, entities, |
|
34
|
|
|
* or entities-with-count? |
|
35
|
|
|
* @param ResourceSet $resourceSet The entity set containing the entities to fetch |
|
36
|
|
|
* @param FilterInfo|null $filterInfo The $filter parameter of the OData query. NULL if absent |
|
37
|
|
|
* @param null|InternalOrderByInfo $orderBy sorted order if we want to get the data in some |
|
38
|
|
|
* specific order |
|
39
|
|
|
* @param int|null $top number of records which need to be retrieved |
|
40
|
|
|
* @param int|null $skip number of records which need to be skipped |
|
41
|
|
|
* @param SkipTokenInfo|null $skipToken value indicating what records to skip |
|
42
|
|
|
* @param string[]|null $eagerLoad array of relations to eager load |
|
43
|
|
|
* @param Model|Relation|null $sourceEntityInstance Starting point of query |
|
44
|
|
|
* |
|
45
|
|
|
* @return QueryResult |
|
46
|
|
|
* @throws InvalidArgumentException |
|
47
|
|
|
* @throws InvalidOperationException |
|
48
|
|
|
* @throws \ReflectionException |
|
49
|
|
|
* @throws ODataException |
|
50
|
|
|
*/ |
|
51
|
|
|
public function getResourceSet( |
|
52
|
|
|
QueryType $queryType, |
|
53
|
|
|
ResourceSet $resourceSet, |
|
54
|
|
|
FilterInfo $filterInfo = null, |
|
55
|
|
|
$orderBy = null, |
|
56
|
|
|
$top = null, |
|
57
|
|
|
$skip = null, |
|
58
|
|
|
SkipTokenInfo $skipToken = null, |
|
59
|
|
|
array $eagerLoad = null, |
|
60
|
|
|
$sourceEntityInstance = null |
|
61
|
|
|
) { |
|
62
|
|
|
$rawLoad = $this->processEagerLoadList($eagerLoad); |
|
63
|
|
|
$modelLoad = []; |
|
64
|
|
|
|
|
65
|
|
|
$this->checkSourceInstance($sourceEntityInstance); |
|
66
|
|
|
if (null == $sourceEntityInstance) { |
|
67
|
|
|
$sourceEntityInstance = $this->getSourceEntityInstance($resourceSet); |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
$keyName = null; |
|
71
|
|
|
$tableName = null; |
|
72
|
|
|
if ($sourceEntityInstance instanceof Model) { |
|
73
|
|
|
$modelLoad = $sourceEntityInstance->getEagerLoad(); |
|
74
|
|
|
$keyName = $sourceEntityInstance->getKeyName(); |
|
75
|
|
|
$tableName = $sourceEntityInstance->getTable(); |
|
76
|
|
|
} elseif ($sourceEntityInstance instanceof Relation) { |
|
77
|
|
|
/** @var MetadataTrait $model */ |
|
78
|
|
|
$model = $sourceEntityInstance->getRelated(); |
|
79
|
|
|
$modelLoad = $model->getEagerLoad(); |
|
80
|
|
|
$keyName = $sourceEntityInstance->getRelated()->getKeyName(); |
|
81
|
|
|
$tableName = $sourceEntityInstance->getRelated()->getTable(); |
|
82
|
|
|
} |
|
83
|
|
|
if (null === $keyName) { |
|
84
|
|
|
throw new InvalidOperationException('Key name not retrieved'); |
|
85
|
|
|
} |
|
86
|
|
|
$rawLoad = array_values(array_unique(array_merge($rawLoad, $modelLoad))); |
|
87
|
|
|
|
|
88
|
|
|
$checkInstance = $sourceEntityInstance instanceof Model ? $sourceEntityInstance : null; |
|
89
|
|
|
$this->checkAuth($sourceEntityInstance, $checkInstance); |
|
90
|
|
|
|
|
91
|
|
|
$result = new QueryResult(); |
|
92
|
|
|
$result->results = null; |
|
93
|
|
|
$result->count = null; |
|
94
|
|
|
|
|
95
|
|
|
if (null != $orderBy) { |
|
96
|
|
|
foreach ($orderBy->getOrderByInfo()->getOrderByPathSegments() as $order) { |
|
97
|
|
|
foreach ($order->getSubPathSegments() as $subOrder) { |
|
98
|
|
|
$subName = $subOrder->getName(); |
|
99
|
|
|
$subName = $tableName.'.'.$subName; |
|
100
|
|
|
$sourceEntityInstance = $sourceEntityInstance->orderBy( |
|
101
|
|
|
$subName, |
|
102
|
|
|
$order->isAscending() ? 'asc' : 'desc' |
|
103
|
|
|
); |
|
104
|
|
|
} |
|
105
|
|
|
} |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
// throttle up for trench run |
|
109
|
|
|
if (null != $skipToken) { |
|
110
|
|
|
$sourceEntityInstance = $this->processSkipToken($skipToken, $sourceEntityInstance); |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
if (!isset($skip)) { |
|
114
|
|
|
$skip = 0; |
|
115
|
|
|
} |
|
116
|
|
|
if (!isset($top)) { |
|
117
|
|
|
$top = PHP_INT_MAX; |
|
118
|
|
|
} |
|
119
|
|
|
|
|
120
|
|
|
$nullFilter = true; |
|
121
|
|
|
$isvalid = null; |
|
122
|
|
|
if (isset($filterInfo)) { |
|
123
|
|
|
$method = 'return ' . $filterInfo->getExpressionAsString() . ';'; |
|
124
|
|
|
$clln = '$' . $resourceSet->getResourceType()->getName(); |
|
125
|
|
|
$isvalid = create_function($clln, $method); |
|
|
|
|
|
|
126
|
|
|
$nullFilter = false; |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
list($bulkSetCount, $resultSet, $resultCount, $skip) = $this->applyFiltering( |
|
130
|
|
|
$top, |
|
131
|
|
|
$skip, |
|
132
|
|
|
$sourceEntityInstance, |
|
133
|
|
|
$nullFilter, |
|
134
|
|
|
$rawLoad, |
|
135
|
|
|
$isvalid |
|
136
|
|
|
); |
|
137
|
|
|
|
|
138
|
|
|
if (isset($top)) { |
|
139
|
|
|
$resultSet = $resultSet->take($top); |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
$qVal = $queryType->getValue(); |
|
143
|
|
|
if (QueryType::ENTITIES()->getValue() == $qVal || QueryType::ENTITIES_WITH_COUNT()->getValue() == $qVal) { |
|
144
|
|
|
$result->results = []; |
|
145
|
|
|
foreach ($resultSet as $res) { |
|
146
|
|
|
$result->results[] = $res; |
|
147
|
|
|
} |
|
148
|
|
|
} |
|
149
|
|
|
if (QueryType::COUNT()->getValue() == $qVal || QueryType::ENTITIES_WITH_COUNT()->getValue() == $qVal) { |
|
150
|
|
|
$result->count = $resultCount; |
|
151
|
|
|
} |
|
152
|
|
|
$hazMore = $bulkSetCount > $skip+count($resultSet); |
|
153
|
|
|
$result->hasMore = $hazMore; |
|
154
|
|
|
return $result; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* Get related resource set for a resource |
|
159
|
|
|
* IE: http://host/EntitySet(1L)/NavigationPropertyToCollection |
|
160
|
|
|
* http://host/EntitySet?$expand=NavigationPropertyToCollection. |
|
161
|
|
|
* |
|
162
|
|
|
* @param QueryType $queryType Is this is a query for a count, entities, or entities-with-count |
|
163
|
|
|
* @param ResourceSet $sourceResourceSet The entity set containing the source entity |
|
164
|
|
|
* @param Model $sourceEntityInstance The source entity instance |
|
165
|
|
|
* @param ResourceSet $targetResourceSet The resource set pointed to by the navigation property |
|
166
|
|
|
* @param ResourceProperty $targetProperty The navigation property to retrieve |
|
167
|
|
|
* @param FilterInfo|null $filter The $filter parameter of the OData query. NULL if none specified |
|
168
|
|
|
* @param mixed|null $orderBy sorted order if we want to get the data in some specific order |
|
169
|
|
|
* @param int|null $top number of records which need to be retrieved |
|
170
|
|
|
* @param int|null $skip number of records which need to be skipped |
|
171
|
|
|
* @param SkipTokenInfo|null $skipToken value indicating what records to skip |
|
172
|
|
|
* |
|
173
|
|
|
* @return QueryResult |
|
174
|
|
|
* @throws InvalidOperationException |
|
175
|
|
|
* @throws ODataException |
|
176
|
|
|
* @throws \ReflectionException |
|
177
|
|
|
*/ |
|
178
|
|
|
public function getRelatedResourceSet( |
|
179
|
|
|
QueryType $queryType, |
|
180
|
|
|
ResourceSet $sourceResourceSet, |
|
181
|
|
|
Model $sourceEntityInstance, |
|
182
|
|
|
/** @noinspection PhpUnusedParameterInspection */ |
|
183
|
|
|
ResourceSet $targetResourceSet, |
|
184
|
|
|
ResourceProperty $targetProperty, |
|
185
|
|
|
FilterInfo $filter = null, |
|
186
|
|
|
$orderBy = null, |
|
187
|
|
|
$top = null, |
|
188
|
|
|
$skip = null, |
|
189
|
|
|
SkipTokenInfo $skipToken = null |
|
190
|
|
|
) { |
|
191
|
|
|
$this->checkAuth($sourceEntityInstance); |
|
192
|
|
|
|
|
193
|
|
|
$propertyName = $targetProperty->getName(); |
|
194
|
|
|
$results = $sourceEntityInstance->$propertyName(); |
|
195
|
|
|
|
|
196
|
|
|
return $this->getResourceSet( |
|
197
|
|
|
$queryType, |
|
198
|
|
|
$sourceResourceSet, |
|
199
|
|
|
$filter, |
|
200
|
|
|
$orderBy, |
|
201
|
|
|
$top, |
|
202
|
|
|
$skip, |
|
203
|
|
|
$skipToken, |
|
204
|
|
|
null, |
|
205
|
|
|
$results |
|
206
|
|
|
); |
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
/** |
|
210
|
|
|
* Gets an entity instance from an entity set identified by a key |
|
211
|
|
|
* IE: http://host/EntitySet(1L) |
|
212
|
|
|
* http://host/EntitySet(KeyA=2L,KeyB='someValue'). |
|
213
|
|
|
* |
|
214
|
|
|
* @param ResourceSet $resourceSet The entity set containing the entity to fetch |
|
215
|
|
|
* @param KeyDescriptor|null $keyDescriptor The key identifying the entity to fetch |
|
216
|
|
|
* @param string[]|null $eagerLoad array of relations to eager load |
|
217
|
|
|
* |
|
218
|
|
|
* @return Model|null Returns entity instance if found else null |
|
219
|
|
|
* @throws \Exception; |
|
220
|
|
|
*/ |
|
221
|
|
|
public function getResourceFromResourceSet( |
|
222
|
|
|
ResourceSet $resourceSet, |
|
223
|
|
|
KeyDescriptor $keyDescriptor = null, |
|
224
|
|
|
array $eagerLoad = null |
|
225
|
|
|
) { |
|
226
|
|
|
return $this->getResource($resourceSet, $keyDescriptor, [], $eagerLoad); |
|
227
|
|
|
} |
|
228
|
|
|
|
|
229
|
|
|
|
|
230
|
|
|
/** |
|
231
|
|
|
* Common method for getResourceFromRelatedResourceSet() and getResourceFromResourceSet(). |
|
232
|
|
|
* |
|
233
|
|
|
* @param ResourceSet|null $resourceSet |
|
234
|
|
|
* @param KeyDescriptor|null $keyDescriptor |
|
235
|
|
|
* @param Model|Relation|null $sourceEntityInstance Starting point of query |
|
236
|
|
|
* @param array $whereCondition |
|
237
|
|
|
* @param string[]|null $eagerLoad array of relations to eager load |
|
238
|
|
|
* |
|
239
|
|
|
* @return Model|null |
|
240
|
|
|
* @throws \Exception |
|
241
|
|
|
*/ |
|
242
|
|
|
public function getResource( |
|
243
|
|
|
ResourceSet $resourceSet = null, |
|
244
|
|
|
KeyDescriptor $keyDescriptor = null, |
|
245
|
|
|
array $whereCondition = [], |
|
246
|
|
|
array $eagerLoad = null, |
|
247
|
|
|
$sourceEntityInstance = null |
|
248
|
|
|
) { |
|
249
|
|
|
if (null == $resourceSet && null == $sourceEntityInstance) { |
|
250
|
|
|
$msg = 'Must supply at least one of a resource set and source entity.'; |
|
251
|
|
|
throw new \Exception($msg); |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
$this->checkSourceInstance($sourceEntityInstance); |
|
255
|
|
|
|
|
256
|
|
|
if (null == $sourceEntityInstance) { |
|
257
|
|
|
$sourceEntityInstance = $this->getSourceEntityInstance(/** @scrutinizer ignore-type */$resourceSet); |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
$this->checkAuth($sourceEntityInstance); |
|
261
|
|
|
$modelLoad = null; |
|
262
|
|
|
if ($sourceEntityInstance instanceof Model) { |
|
263
|
|
|
$modelLoad = $sourceEntityInstance->getEagerLoad(); |
|
264
|
|
|
} elseif ($sourceEntityInstance instanceof Relation) { |
|
265
|
|
|
/** @var MetadataTrait $model */ |
|
266
|
|
|
$model = $sourceEntityInstance->getRelated(); |
|
267
|
|
|
$modelLoad = $model->getEagerLoad(); |
|
268
|
|
|
} |
|
269
|
|
|
if (!(isset($modelLoad))) { |
|
270
|
|
|
throw new InvalidOperationException(''); |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
$this->processKeyDescriptor($sourceEntityInstance, $keyDescriptor); |
|
274
|
|
|
foreach ($whereCondition as $fieldName => $fieldValue) { |
|
275
|
|
|
$sourceEntityInstance = $sourceEntityInstance->where($fieldName, $fieldValue); |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
$sourceEntityInstance = $sourceEntityInstance->get(); |
|
279
|
|
|
$sourceCount = $sourceEntityInstance->count(); |
|
280
|
|
|
if (0 == $sourceCount) { |
|
281
|
|
|
return null; |
|
282
|
|
|
} |
|
283
|
|
|
$result = $sourceEntityInstance->first(); |
|
284
|
|
|
|
|
285
|
|
|
return $result; |
|
286
|
|
|
} |
|
287
|
|
|
|
|
288
|
|
|
/** |
|
289
|
|
|
* Get related resource for a resource |
|
290
|
|
|
* IE: http://host/EntitySet(1L)/NavigationPropertyToSingleEntity |
|
291
|
|
|
* http://host/EntitySet?$expand=NavigationPropertyToSingleEntity. |
|
292
|
|
|
* |
|
293
|
|
|
* @param ResourceSet $sourceResourceSet The entity set containing the source entity |
|
294
|
|
|
* @param Model $sourceEntityInstance the source entity instance |
|
295
|
|
|
* @param ResourceSet $targetResourceSet The entity set containing the entity pointed to by the nav property |
|
296
|
|
|
* @param ResourceProperty $targetProperty The navigation property to fetch |
|
297
|
|
|
* |
|
298
|
|
|
* @return Model|null The related resource if found else null |
|
299
|
|
|
* @throws ODataException |
|
300
|
|
|
* @throws InvalidOperationException |
|
301
|
|
|
* @throws \ReflectionException |
|
302
|
|
|
*/ |
|
303
|
|
|
public function getRelatedResourceReference( |
|
304
|
|
|
/** @noinspection PhpUnusedParameterInspection */ |
|
305
|
|
|
ResourceSet $sourceResourceSet, |
|
306
|
|
|
Model $sourceEntityInstance, |
|
307
|
|
|
ResourceSet $targetResourceSet, |
|
308
|
|
|
ResourceProperty $targetProperty |
|
309
|
|
|
) { |
|
310
|
|
|
$this->checkAuth($sourceEntityInstance); |
|
311
|
|
|
|
|
312
|
|
|
$propertyName = $targetProperty->getName(); |
|
313
|
|
|
$propertyName = $this->getLaravelRelationName($propertyName); |
|
314
|
|
|
$result = $sourceEntityInstance->$propertyName()->first(); |
|
315
|
|
|
if (null === $result) { |
|
316
|
|
|
return null; |
|
317
|
|
|
} |
|
318
|
|
|
if (!$result instanceof Model) { |
|
319
|
|
|
throw new InvalidOperationException('Model not retrieved from Eloquent relation'); |
|
320
|
|
|
} |
|
321
|
|
|
if ($targetProperty->getResourceType()->getInstanceType()->getName() != get_class($result)) { |
|
322
|
|
|
return null; |
|
323
|
|
|
} |
|
324
|
|
|
return $result; |
|
325
|
|
|
} |
|
326
|
|
|
|
|
327
|
|
|
/** |
|
328
|
|
|
* Gets a related entity instance from an entity set identified by a key |
|
329
|
|
|
* IE: http://host/EntitySet(1L)/NavigationPropertyToCollection(33). |
|
330
|
|
|
* |
|
331
|
|
|
* @param ResourceSet $sourceResourceSet The entity set containing the source entity |
|
332
|
|
|
* @param Model $sourceEntityInstance the source entity instance |
|
333
|
|
|
* @param ResourceSet $targetResourceSet The entity set containing the entity to fetch |
|
334
|
|
|
* @param ResourceProperty $targetProperty the metadata of the target property |
|
335
|
|
|
* @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch |
|
336
|
|
|
* |
|
337
|
|
|
* @return Model|null Returns entity instance if found else null |
|
338
|
|
|
* @throws InvalidOperationException |
|
339
|
|
|
* @throws \Exception |
|
340
|
|
|
*/ |
|
341
|
|
|
public function getResourceFromRelatedResourceSet( |
|
342
|
|
|
/** @noinspection PhpUnusedParameterInspection */ |
|
343
|
|
|
ResourceSet $sourceResourceSet, |
|
344
|
|
|
Model $sourceEntityInstance, |
|
345
|
|
|
ResourceSet $targetResourceSet, |
|
346
|
|
|
ResourceProperty $targetProperty, |
|
347
|
|
|
KeyDescriptor $keyDescriptor |
|
348
|
|
|
) { |
|
349
|
|
|
$propertyName = $targetProperty->getName(); |
|
350
|
|
|
if (!method_exists($sourceEntityInstance, $propertyName)) { |
|
351
|
|
|
$msg = 'Relation method, ' . $propertyName . ', does not exist on supplied entity.'; |
|
352
|
|
|
throw new InvalidArgumentException($msg); |
|
353
|
|
|
} |
|
354
|
|
|
// take key descriptor and turn it into where clause here, rather than in getResource call |
|
355
|
|
|
$sourceEntityInstance = $sourceEntityInstance->$propertyName(); |
|
356
|
|
|
$this->processKeyDescriptor($sourceEntityInstance, $keyDescriptor); |
|
357
|
|
|
$result = $this->getResource(null, null, [], [], $sourceEntityInstance); |
|
358
|
|
|
if (!(null == $result || $result instanceof Model)) { |
|
|
|
|
|
|
359
|
|
|
$msg = 'GetResourceFromRelatedResourceSet must return an entity or null'; |
|
360
|
|
|
throw new InvalidOperationException($msg); |
|
361
|
|
|
} |
|
362
|
|
|
return $result; |
|
363
|
|
|
} |
|
364
|
|
|
|
|
365
|
|
|
/** |
|
366
|
|
|
* @param ResourceSet $resourceSet |
|
367
|
|
|
* @return mixed |
|
368
|
|
|
* @throws \ReflectionException |
|
369
|
|
|
*/ |
|
370
|
|
|
protected function getSourceEntityInstance(ResourceSet $resourceSet) |
|
371
|
|
|
{ |
|
372
|
|
|
$entityClassName = $resourceSet->getResourceType()->getInstanceType()->name; |
|
373
|
|
|
return App::make($entityClassName); |
|
374
|
|
|
} |
|
375
|
|
|
|
|
376
|
|
|
/** |
|
377
|
|
|
* @param Model|Relation|null $source |
|
378
|
|
|
*/ |
|
379
|
|
|
protected function checkSourceInstance($source) |
|
380
|
|
|
{ |
|
381
|
|
|
if (!(null == $source || $source instanceof Model || $source instanceof Relation)) { |
|
|
|
|
|
|
382
|
|
|
$msg = 'Source entity instance must be null, a model, or a relation.'; |
|
383
|
|
|
throw new InvalidArgumentException($msg); |
|
384
|
|
|
} |
|
385
|
|
|
} |
|
386
|
|
|
|
|
387
|
|
|
/** |
|
388
|
|
|
* @param Model|Relation|null $sourceEntityInstance |
|
389
|
|
|
* @param null|mixed $checkInstance |
|
390
|
|
|
* |
|
391
|
|
|
* @throws ODataException |
|
392
|
|
|
*/ |
|
393
|
|
|
private function checkAuth($sourceEntityInstance, $checkInstance = null) |
|
394
|
|
|
{ |
|
395
|
|
|
$check = $checkInstance instanceof Model ? $checkInstance |
|
396
|
|
|
: $checkInstance instanceof Relation ? $checkInstance |
|
397
|
|
|
: $sourceEntityInstance instanceof Model ? $sourceEntityInstance |
|
398
|
|
|
: $sourceEntityInstance instanceof Relation ? $sourceEntityInstance |
|
399
|
|
|
: null; |
|
400
|
|
|
if (!$this->getAuth()->canAuth(ActionVerb::READ(), $sourceEntityInstance, $check)) { |
|
401
|
|
|
throw new ODataException('Access denied', 403); |
|
402
|
|
|
} |
|
403
|
|
|
} |
|
404
|
|
|
|
|
405
|
|
|
/** |
|
406
|
|
|
* @param Model|Builder $sourceEntityInstance |
|
407
|
|
|
* @param KeyDescriptor|null $keyDescriptor |
|
408
|
|
|
* @throws InvalidOperationException |
|
409
|
|
|
*/ |
|
410
|
|
|
private function processKeyDescriptor(&$sourceEntityInstance, KeyDescriptor $keyDescriptor = null) |
|
411
|
|
|
{ |
|
412
|
|
|
if ($keyDescriptor) { |
|
413
|
|
|
$table = ($sourceEntityInstance instanceof Model) ? $sourceEntityInstance->getTable().'.' : ''; |
|
414
|
|
|
foreach ($keyDescriptor->getValidatedNamedValues() as $key => $value) { |
|
415
|
|
|
$trimValue = trim($value[0], '\''); |
|
416
|
|
|
$sourceEntityInstance = $sourceEntityInstance->where($table.$key, $trimValue); |
|
417
|
|
|
} |
|
418
|
|
|
} |
|
419
|
|
|
} |
|
420
|
|
|
|
|
421
|
|
|
/** |
|
422
|
|
|
* @param string[]|null $eagerLoad |
|
423
|
|
|
* @return array |
|
424
|
|
|
* @throws InvalidOperationException |
|
425
|
|
|
*/ |
|
426
|
|
|
private function processEagerLoadList(array $eagerLoad = null) |
|
427
|
|
|
{ |
|
428
|
|
|
$load = (null === $eagerLoad) ? [] : $eagerLoad; |
|
429
|
|
|
$rawLoad = []; |
|
430
|
|
|
foreach ($load as $line) { |
|
431
|
|
|
if (!is_string($line)) { |
|
432
|
|
|
throw new InvalidOperationException('Eager-load elements must be non-empty strings'); |
|
433
|
|
|
} |
|
434
|
|
|
$lineParts = explode('/', $line); |
|
435
|
|
|
$numberOfParts = count($lineParts); |
|
436
|
|
|
for ($i = 0; $i<$numberOfParts; $i++) { |
|
437
|
|
|
$lineParts[$i] = $this->getLaravelRelationName($lineParts[$i]); |
|
438
|
|
|
} |
|
439
|
|
|
$remixLine = implode('.', $lineParts); |
|
440
|
|
|
$rawLoad[] = $remixLine; |
|
441
|
|
|
} |
|
442
|
|
|
return $rawLoad; |
|
443
|
|
|
} |
|
444
|
|
|
|
|
445
|
|
|
/** |
|
446
|
|
|
* @param string $odataProperty |
|
447
|
|
|
* @return string |
|
448
|
|
|
*/ |
|
449
|
|
|
private function getLaravelRelationName($odataProperty) |
|
450
|
|
|
{ |
|
451
|
|
|
$laravelProperty = $odataProperty; |
|
452
|
|
|
$pos = strrpos($laravelProperty, '_'); |
|
453
|
|
|
if ($pos !== false) { |
|
454
|
|
|
$laravelProperty = substr($laravelProperty, 0, $pos); |
|
455
|
|
|
} |
|
456
|
|
|
return $laravelProperty; |
|
457
|
|
|
} |
|
458
|
|
|
|
|
459
|
|
|
/** |
|
460
|
|
|
* @param SkipTokenInfo $skipToken |
|
461
|
|
|
* @param Model|Builder $sourceEntityInstance |
|
462
|
|
|
* @return mixed |
|
463
|
|
|
* @throws InvalidOperationException |
|
464
|
|
|
*/ |
|
465
|
|
|
protected function processSkipToken(SkipTokenInfo $skipToken, $sourceEntityInstance) |
|
466
|
|
|
{ |
|
467
|
|
|
$parameters = []; |
|
468
|
|
|
$processed = []; |
|
469
|
|
|
$segments = $skipToken->getOrderByInfo()->getOrderByPathSegments(); |
|
470
|
|
|
$values = $skipToken->getOrderByKeysInToken(); |
|
471
|
|
|
$numValues = count($values); |
|
472
|
|
|
if ($numValues != count($segments)) { |
|
473
|
|
|
$msg = 'Expected '.count($segments).', got '.$numValues; |
|
474
|
|
|
throw new InvalidOperationException($msg); |
|
475
|
|
|
} |
|
476
|
|
|
|
|
477
|
|
|
for ($i = 0; $i < $numValues; $i++) { |
|
478
|
|
|
$relation = $segments[$i]->isAscending() ? '>' : '<'; |
|
479
|
|
|
$name = $segments[$i]->getSubPathSegments()[0]->getName(); |
|
480
|
|
|
$parameters[$name] = ['direction' => $relation, 'value' => trim($values[$i][0], '\'')]; |
|
481
|
|
|
} |
|
482
|
|
|
|
|
483
|
|
|
foreach ($parameters as $name => $line) { |
|
484
|
|
|
$processed[$name] = ['direction' => $line['direction'], 'value' => $line['value']]; |
|
485
|
|
|
$sourceEntityInstance = $sourceEntityInstance |
|
486
|
|
|
->orWhere( |
|
487
|
|
|
function (Builder $query) use ($processed) { |
|
488
|
|
|
foreach ($processed as $key => $proc) { |
|
489
|
|
|
$query->where($key, $proc['direction'], $proc['value']); |
|
490
|
|
|
} |
|
491
|
|
|
} |
|
492
|
|
|
); |
|
493
|
|
|
// now we've handled the later-in-order segment for this key, drop it back to equality in prep |
|
494
|
|
|
// for next key - same-in-order for processed keys and later-in-order for next |
|
495
|
|
|
$processed[$name]['direction'] = '='; |
|
496
|
|
|
} |
|
497
|
|
|
return $sourceEntityInstance; |
|
498
|
|
|
} |
|
499
|
|
|
|
|
500
|
|
|
/** |
|
501
|
|
|
* @param $top |
|
502
|
|
|
* @param $skip |
|
503
|
|
|
* @param Model|Builder $sourceEntityInstance |
|
504
|
|
|
* @param $nullFilter |
|
505
|
|
|
* @param $rawLoad |
|
506
|
|
|
* @param callable|null $isvalid |
|
507
|
|
|
* @return array |
|
508
|
|
|
* @throws InvalidOperationException |
|
509
|
|
|
*/ |
|
510
|
|
|
protected function applyFiltering( |
|
511
|
|
|
$top, |
|
512
|
|
|
$skip, |
|
513
|
|
|
$sourceEntityInstance, |
|
514
|
|
|
$nullFilter, |
|
515
|
|
|
$rawLoad, |
|
516
|
|
|
callable $isvalid = null |
|
517
|
|
|
) { |
|
518
|
|
|
$bulkSetCount = $sourceEntityInstance->count(); |
|
519
|
|
|
$bigSet = 20000 < $bulkSetCount; |
|
520
|
|
|
|
|
521
|
|
|
if ($nullFilter) { |
|
522
|
|
|
// default no-filter case, palm processing off to database engine - is a lot faster |
|
523
|
|
|
$resultSet = $sourceEntityInstance->skip($skip)->take($top)->with($rawLoad)->get(); |
|
524
|
|
|
$resultCount = $bulkSetCount; |
|
525
|
|
|
} elseif ($bigSet) { |
|
526
|
|
|
if (!(isset($isvalid))) { |
|
527
|
|
|
$msg = 'Filter closure not set'; |
|
528
|
|
|
throw new InvalidOperationException($msg); |
|
529
|
|
|
} |
|
530
|
|
|
$resultSet = new Collection([]); |
|
531
|
|
|
$rawCount = 0; |
|
532
|
|
|
$rawTop = null === $top ? $bulkSetCount : $top; |
|
533
|
|
|
|
|
534
|
|
|
// loop thru, chunk by chunk, to reduce chances of exhausting memory |
|
535
|
|
|
$sourceEntityInstance->chunk( |
|
536
|
|
|
5000, |
|
537
|
|
|
function (Collection $results) use ($isvalid, &$skip, &$resultSet, &$rawCount, $rawTop) { |
|
538
|
|
|
// apply filter |
|
539
|
|
|
$results = $results->filter($isvalid); |
|
540
|
|
|
// need to iterate through full result set to find count of items matching filter, |
|
541
|
|
|
// so we can't bail out early |
|
542
|
|
|
$rawCount += $results->count(); |
|
543
|
|
|
// now bolt on filtrate to accumulating result set if we haven't accumulated enough bitz |
|
544
|
|
|
if ($rawTop > $resultSet->count() + $skip) { |
|
545
|
|
|
$resultSet = collect(array_merge($resultSet->all(), $results->all())); |
|
546
|
|
|
$sliceAmount = min($skip, $resultSet->count()); |
|
547
|
|
|
$resultSet = $resultSet->slice($sliceAmount); |
|
548
|
|
|
$skip -= $sliceAmount; |
|
549
|
|
|
} |
|
550
|
|
|
} |
|
551
|
|
|
); |
|
552
|
|
|
|
|
553
|
|
|
// clean up residual to-be-skipped records |
|
554
|
|
|
$resultSet = $resultSet->slice($skip); |
|
555
|
|
|
$resultCount = $rawCount; |
|
556
|
|
|
} else { |
|
557
|
|
|
if ($sourceEntityInstance instanceof Model) { |
|
558
|
|
|
/** @var Builder $sourceEntityInstance */ |
|
559
|
|
|
$sourceEntityInstance = $sourceEntityInstance->getQuery(); |
|
560
|
|
|
} |
|
561
|
|
|
/** @var Collection $resultSet */ |
|
562
|
|
|
$resultSet = $sourceEntityInstance->with($rawLoad)->get(); |
|
563
|
|
|
$resultSet = $resultSet->filter($isvalid); |
|
564
|
|
|
$resultCount = $resultSet->count(); |
|
565
|
|
|
|
|
566
|
|
|
if (isset($skip)) { |
|
567
|
|
|
$resultSet = $resultSet->slice($skip); |
|
568
|
|
|
} |
|
569
|
|
|
} |
|
570
|
|
|
return [$bulkSetCount, $resultSet, $resultCount, $skip]; |
|
571
|
|
|
} |
|
572
|
|
|
} |
|
573
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.