Passed
Pull Request — master (#182)
by Alex
06:05
created

LaravelReadQuery::getAuth()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 1
nc 1
nop 0
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
64
        $sourceEntityInstance = $this->checkSourceInstance($sourceEntityInstance, $resourceSet);
65
66
        /** @var MetadataTrait $model */
67
        $model = $sourceEntityInstance instanceof Model ? $sourceEntityInstance : $sourceEntityInstance->getRelated();
68
        $modelLoad = $model->getEagerLoad();
69
        $keyName = $model->getKeyName();
70
        $tableName = $model->getTable();
71
72
        if (null === $keyName) {
0 ignored issues
show
introduced by
The condition null === $keyName is always false.
Loading history...
73
            throw new InvalidOperationException('Key name not retrieved');
74
        }
75
        $rawLoad = array_values(array_unique(array_merge($rawLoad, $modelLoad)));
76
77
        $checkInstance = $sourceEntityInstance instanceof Model ? $sourceEntityInstance : null;
78
        $this->checkAuth($sourceEntityInstance, $checkInstance);
79
80
        $result          = new QueryResult();
81
        $result->results = null;
82
        $result->count   = null;
83
84
        if (null != $orderBy) {
85
            foreach ($orderBy->getOrderByInfo()->getOrderByPathSegments() as $order) {
86
                foreach ($order->getSubPathSegments() as $subOrder) {
87
                    $subName = $subOrder->getName();
88
                    $subName = $tableName.'.'.$subName;
89
                    $sourceEntityInstance = $sourceEntityInstance->orderBy(
90
                        $subName,
91
                        $order->isAscending() ? 'asc' : 'desc'
92
                    );
93
                }
94
            }
95
        }
96
97
        // throttle up for trench run
98
        if (null != $skipToken) {
99
            $sourceEntityInstance = $this->processSkipToken($skipToken, $sourceEntityInstance);
100
        }
101
102
        if (!isset($skip)) {
103
            $skip = 0;
104
        }
105
        if (!isset($top)) {
106
            $top = PHP_INT_MAX;
107
        }
108
109
        $nullFilter = true;
110
        $isvalid = null;
111
        if (isset($filterInfo)) {
112
            $method = 'return ' . $filterInfo->getExpressionAsString() . ';';
113
            $clln = '$' . $resourceSet->getResourceType()->getName();
114
            $isvalid = create_function($clln, $method);
0 ignored issues
show
Deprecated Code introduced by
The function create_function() has been deprecated: 7.2 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

114
            $isvalid = /** @scrutinizer ignore-deprecated */ create_function($clln, $method);

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.

Loading history...
115
            $nullFilter = false;
116
        }
117
118
        list($bulkSetCount, $resultSet, $resultCount, $skip) = $this->applyFiltering(
119
            $top,
120
            $skip,
121
            $sourceEntityInstance,
122
            $nullFilter,
123
            $rawLoad,
124
            $isvalid
125
        );
126
127
        if (isset($top)) {
128
            $resultSet = $resultSet->take($top);
129
        }
130
131
        $qVal = $queryType->getValue();
132
        if (QueryType::ENTITIES()->getValue() == $qVal || QueryType::ENTITIES_WITH_COUNT()->getValue() == $qVal) {
133
            $result->results = [];
134
            foreach ($resultSet as $res) {
135
                $result->results[] = $res;
136
            }
137
        }
138
        if (QueryType::COUNT()->getValue() == $qVal || QueryType::ENTITIES_WITH_COUNT()->getValue() == $qVal) {
139
            $result->count = $resultCount;
140
        }
141
        $hazMore = $bulkSetCount > $skip+count($resultSet);
142
        $result->hasMore = $hazMore;
143
        return $result;
144
    }
145
146
    /**
147
     * Get related resource set for a resource
148
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection
149
     * http://host/EntitySet?$expand=NavigationPropertyToCollection.
150
     *
151
     * @param QueryType          $queryType            Is this is a query for a count, entities, or entities-with-count
152
     * @param ResourceSet        $sourceResourceSet    The entity set containing the source entity
153
     * @param Model              $sourceEntityInstance The source entity instance
154
     * @param ResourceSet        $targetResourceSet    The resource set pointed to by the navigation property
155
     * @param ResourceProperty   $targetProperty       The navigation property to retrieve
156
     * @param FilterInfo|null    $filter               The $filter parameter of the OData query.  NULL if none specified
157
     * @param mixed|null         $orderBy              sorted order if we want to get the data in some specific order
158
     * @param int|null           $top                  number of records which need to be retrieved
159
     * @param int|null           $skip                 number of records which need to be skipped
160
     * @param SkipTokenInfo|null $skipToken            value indicating what records to skip
161
     *
162
     * @return QueryResult
163
     * @throws InvalidOperationException
164
     * @throws ODataException
165
     * @throws \ReflectionException
166
     */
167
    public function getRelatedResourceSet(
168
        QueryType $queryType,
169
        ResourceSet $sourceResourceSet,
170
        Model $sourceEntityInstance,
171
        /** @noinspection PhpUnusedParameterInspection */
172
        ResourceSet $targetResourceSet,
173
        ResourceProperty $targetProperty,
174
        FilterInfo $filter = null,
175
        $orderBy = null,
176
        $top = null,
177
        $skip = null,
178
        SkipTokenInfo $skipToken = null
179
    ) {
180
        $this->checkAuth($sourceEntityInstance);
181
182
        $propertyName = $targetProperty->getName();
183
        $results = $sourceEntityInstance->$propertyName();
184
185
        return $this->getResourceSet(
186
            $queryType,
187
            $sourceResourceSet,
188
            $filter,
189
            $orderBy,
190
            $top,
191
            $skip,
192
            $skipToken,
193
            null,
194
            $results
195
        );
196
    }
197
198
    /**
199
     * Gets an entity instance from an entity set identified by a key
200
     * IE: http://host/EntitySet(1L)
201
     * http://host/EntitySet(KeyA=2L,KeyB='someValue').
202
     *
203
     * @param ResourceSet        $resourceSet   The entity set containing the entity to fetch
204
     * @param KeyDescriptor|null $keyDescriptor The key identifying the entity to fetch
205
     * @param string[]|null      $eagerLoad     array of relations to eager load
206
     *
207
     * @return Model|null Returns entity instance if found else null
208
     * @throws \Exception;
209
     */
210
    public function getResourceFromResourceSet(
211
        ResourceSet $resourceSet,
212
        KeyDescriptor $keyDescriptor = null,
213
        array $eagerLoad = null
214
    ) {
215
        return $this->getResource($resourceSet, $keyDescriptor, [], $eagerLoad);
216
    }
217
218
219
    /**
220
     * Common method for getResourceFromRelatedResourceSet() and getResourceFromResourceSet().
221
     *
222
     * @param ResourceSet|null    $resourceSet
223
     * @param KeyDescriptor|null  $keyDescriptor
224
     * @param Model|Relation|null $sourceEntityInstance Starting point of query
225
     * @param array               $whereCondition
226
     * @param string[]|null       $eagerLoad            array of relations to eager load
227
     *
228
     * @return Model|null
229
     * @throws \Exception
230
     */
231
    public function getResource(
232
        ResourceSet $resourceSet = null,
233
        KeyDescriptor $keyDescriptor = null,
234
        array $whereCondition = [],
235
        array $eagerLoad = null,
236
        $sourceEntityInstance = null
237
    ) {
238
        if (null == $resourceSet && null == $sourceEntityInstance) {
239
            $msg = 'Must supply at least one of a resource set and source entity.';
240
            throw new \Exception($msg);
241
        }
242
243
        $sourceEntityInstance = $this->checkSourceInstance($sourceEntityInstance, $resourceSet);
244
245
        $this->checkAuth($sourceEntityInstance);
246
        $modelLoad = null;
247
        if ($sourceEntityInstance instanceof Model) {
248
            $modelLoad = $sourceEntityInstance->getEagerLoad();
249
        } elseif ($sourceEntityInstance instanceof Relation) {
250
            /** @var MetadataTrait $model */
251
            $model = $sourceEntityInstance->getRelated();
252
            $modelLoad = $model->getEagerLoad();
253
        }
254
        if (!(isset($modelLoad))) {
255
            throw new InvalidOperationException('');
256
        }
257
258
        $this->processKeyDescriptor($sourceEntityInstance, $keyDescriptor);
0 ignored issues
show
Bug introduced by
It seems like $sourceEntityInstance can also be of type Illuminate\Database\Eloquent\Relations\Relation; however, parameter $sourceEntityInstance of AlgoWeb\PODataLaravel\Qu...:processKeyDescriptor() does only seem to accept Illuminate\Database\Eloq...Database\Eloquent\Model, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

258
        $this->processKeyDescriptor(/** @scrutinizer ignore-type */ $sourceEntityInstance, $keyDescriptor);
Loading history...
259
        foreach ($whereCondition as $fieldName => $fieldValue) {
260
            $sourceEntityInstance = $sourceEntityInstance->where($fieldName, $fieldValue);
261
        }
262
263
        $sourceEntityInstance = $sourceEntityInstance->get();
264
        $sourceCount = $sourceEntityInstance->count();
265
        if (0 == $sourceCount) {
266
            return null;
267
        }
268
        $result = $sourceEntityInstance->first();
269
270
        return $result;
271
    }
272
273
    /**
274
     * Get related resource for a resource
275
     * IE: http://host/EntitySet(1L)/NavigationPropertyToSingleEntity
276
     * http://host/EntitySet?$expand=NavigationPropertyToSingleEntity.
277
     *
278
     * @param ResourceSet      $sourceResourceSet    The entity set containing the source entity
279
     * @param Model            $sourceEntityInstance the source entity instance
280
     * @param ResourceSet      $targetResourceSet    The entity set containing the entity pointed to by the nav property
281
     * @param ResourceProperty $targetProperty       The navigation property to fetch
282
     *
283
     * @return Model|null The related resource if found else null
284
     * @throws ODataException
285
     * @throws InvalidOperationException
286
     * @throws \ReflectionException
287
     */
288
    public function getRelatedResourceReference(
289
        /** @noinspection PhpUnusedParameterInspection */
290
        ResourceSet $sourceResourceSet,
291
        Model $sourceEntityInstance,
292
        ResourceSet $targetResourceSet,
293
        ResourceProperty $targetProperty
294
    ) {
295
        $this->checkAuth($sourceEntityInstance);
296
297
        $propertyName = $targetProperty->getName();
298
        $propertyName = $this->getLaravelRelationName($propertyName);
299
        $result = $sourceEntityInstance->$propertyName()->first();
300
        if (null === $result) {
301
            return null;
302
        }
303
        if (!$result instanceof Model) {
304
            throw new InvalidOperationException('Model not retrieved from Eloquent relation');
305
        }
306
        if ($targetProperty->getResourceType()->getInstanceType()->getName() != get_class($result)) {
307
            return null;
308
        }
309
        return $result;
310
    }
311
312
    /**
313
     * Gets a related entity instance from an entity set identified by a key
314
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection(33).
315
     *
316
     * @param ResourceSet      $sourceResourceSet    The entity set containing the source entity
317
     * @param Model            $sourceEntityInstance the source entity instance
318
     * @param ResourceSet      $targetResourceSet    The entity set containing the entity to fetch
319
     * @param ResourceProperty $targetProperty       the metadata of the target property
320
     * @param KeyDescriptor    $keyDescriptor        The key identifying the entity to fetch
321
     *
322
     * @return Model|null Returns entity instance if found else null
323
     * @throws InvalidOperationException
324
     * @throws \Exception
325
     */
326
    public function getResourceFromRelatedResourceSet(
327
        /** @noinspection PhpUnusedParameterInspection */
328
        ResourceSet $sourceResourceSet,
329
        Model $sourceEntityInstance,
330
        ResourceSet $targetResourceSet,
331
        ResourceProperty $targetProperty,
332
        KeyDescriptor $keyDescriptor
333
    ) {
334
        $propertyName = $targetProperty->getName();
335
        if (!method_exists($sourceEntityInstance, $propertyName)) {
336
            $msg = 'Relation method, ' . $propertyName . ', does not exist on supplied entity.';
337
            throw new InvalidArgumentException($msg);
338
        }
339
        // take key descriptor and turn it into where clause here, rather than in getResource call
340
        $sourceEntityInstance = $sourceEntityInstance->$propertyName();
341
        $this->processKeyDescriptor($sourceEntityInstance, $keyDescriptor);
342
        $result = $this->getResource(null, null, [], [], $sourceEntityInstance);
343
        if (!(null == $result || $result instanceof Model)) {
0 ignored issues
show
introduced by
$result is always a sub-type of Illuminate\Database\Eloquent\Model.
Loading history...
344
            $msg = 'GetResourceFromRelatedResourceSet must return an entity or null';
345
            throw new InvalidOperationException($msg);
346
        }
347
        return $result;
348
    }
349
350
    /**
351
     * @param  ResourceSet $resourceSet
352
     * @return mixed
353
     * @throws \ReflectionException
354
     */
355
    protected function getSourceEntityInstance(ResourceSet $resourceSet)
356
    {
357
        $entityClassName = $resourceSet->getResourceType()->getInstanceType()->name;
358
        return App::make($entityClassName);
359
    }
360
361
    /**
362
     * @param Model|Relation|null $source
363
     * @param ResourceSet|null $resourceSet
364
     * @return Model|Relation|mixed|null
365
     * @throws \ReflectionException
366
     */
367
    protected function checkSourceInstance($source, ResourceSet $resourceSet = null)
368
    {
369
        if (!(null == $source || $source instanceof Model || $source instanceof Relation)) {
0 ignored issues
show
introduced by
$source is always a sub-type of Illuminate\Database\Eloquent\Relations\Relation.
Loading history...
370
            $msg = 'Source entity instance must be null, a model, or a relation.';
371
            throw new InvalidArgumentException($msg);
372
        }
373
374
        if (null == $source) {
375
            $source = $this->getSourceEntityInstance($resourceSet);
0 ignored issues
show
Bug introduced by
It seems like $resourceSet can also be of type null; however, parameter $resourceSet of AlgoWeb\PODataLaravel\Qu...tSourceEntityInstance() does only seem to accept POData\Providers\Metadata\ResourceSet, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

375
            $source = $this->getSourceEntityInstance(/** @scrutinizer ignore-type */ $resourceSet);
Loading history...
376
        }
377
378
        return $source;
379
    }
380
381
    /**
382
     * @param Model|Relation|null $sourceEntityInstance
383
     * @param null|mixed $checkInstance
384
     *
385
     * @throws ODataException
386
     */
387
    private function checkAuth($sourceEntityInstance, $checkInstance = null)
388
    {
389
        $check = $checkInstance instanceof Model ? $checkInstance
390
            : $checkInstance instanceof Relation ? $checkInstance
391
                : $sourceEntityInstance instanceof Model ? $sourceEntityInstance
392
                    : $sourceEntityInstance instanceof Relation ? $sourceEntityInstance
393
                        : null;
394
        if (!$this->getAuth()->canAuth(ActionVerb::READ(), $sourceEntityInstance, $check)) {
395
            throw new ODataException('Access denied', 403);
396
        }
397
    }
398
399
    /**
400
     * @param Model|Builder $sourceEntityInstance
401
     * @param  KeyDescriptor|null        $keyDescriptor
402
     * @throws InvalidOperationException
403
     */
404
    private function processKeyDescriptor(&$sourceEntityInstance, KeyDescriptor $keyDescriptor = null)
405
    {
406
        if ($keyDescriptor) {
407
            $table = ($sourceEntityInstance instanceof Model) ? $sourceEntityInstance->getTable().'.' : '';
408
            foreach ($keyDescriptor->getValidatedNamedValues() as $key => $value) {
409
                $trimValue = trim($value[0], '\'');
410
                $sourceEntityInstance = $sourceEntityInstance->where($table.$key, $trimValue);
411
            }
412
        }
413
    }
414
415
    /**
416
     * @param  string[]|null $eagerLoad
417
     * @return array
418
     * @throws InvalidOperationException
419
     */
420
    private function processEagerLoadList(array $eagerLoad = null)
421
    {
422
        $load = (null === $eagerLoad) ? [] : $eagerLoad;
423
        $rawLoad = [];
424
        foreach ($load as $line) {
425
            if (!is_string($line)) {
426
                throw new InvalidOperationException('Eager-load elements must be non-empty strings');
427
            }
428
            $lineParts = explode('/', $line);
429
            $numberOfParts = count($lineParts);
430
            for ($i = 0; $i<$numberOfParts; $i++) {
431
                $lineParts[$i] = $this->getLaravelRelationName($lineParts[$i]);
432
            }
433
            $remixLine = implode('.', $lineParts);
434
            $rawLoad[] = $remixLine;
435
        }
436
        return $rawLoad;
437
    }
438
439
    /**
440
     * @param  string $odataProperty
441
     * @return string
442
     */
443
    private function getLaravelRelationName($odataProperty)
444
    {
445
        $laravelProperty = $odataProperty;
446
        $pos = strrpos($laravelProperty, '_');
447
        if ($pos !== false) {
448
            $laravelProperty = substr($laravelProperty, 0, $pos);
449
        }
450
        return $laravelProperty;
451
    }
452
453
    /**
454
     * @param SkipTokenInfo $skipToken
455
     * @param Model|Builder $sourceEntityInstance
456
     * @return mixed
457
     * @throws InvalidOperationException
458
     */
459
    protected function processSkipToken(SkipTokenInfo $skipToken, $sourceEntityInstance)
460
    {
461
        $parameters = [];
462
        $processed = [];
463
        $segments = $skipToken->getOrderByInfo()->getOrderByPathSegments();
464
        $values = $skipToken->getOrderByKeysInToken();
465
        $numValues = count($values);
466
        if ($numValues != count($segments)) {
467
            $msg = 'Expected '.count($segments).', got '.$numValues;
468
            throw new InvalidOperationException($msg);
469
        }
470
471
        for ($i = 0; $i < $numValues; $i++) {
472
            $relation = $segments[$i]->isAscending() ? '>' : '<';
473
            $name = $segments[$i]->getSubPathSegments()[0]->getName();
474
            $parameters[$name] = ['direction' => $relation, 'value' => trim($values[$i][0], '\'')];
475
        }
476
477
        foreach ($parameters as $name => $line) {
478
            $processed[$name] = ['direction' => $line['direction'], 'value' => $line['value']];
479
            $sourceEntityInstance = $sourceEntityInstance
480
                ->orWhere(
481
                    function (Builder $query) use ($processed) {
482
                        foreach ($processed as $key => $proc) {
483
                            $query->where($key, $proc['direction'], $proc['value']);
484
                        }
485
                    }
486
                );
487
            // now we've handled the later-in-order segment for this key, drop it back to equality in prep
488
            // for next key - same-in-order for processed keys and later-in-order for next
489
            $processed[$name]['direction'] = '=';
490
        }
491
        return $sourceEntityInstance;
492
    }
493
494
    /**
495
     * @param $top
496
     * @param $skip
497
     * @param Model|Builder $sourceEntityInstance
498
     * @param $nullFilter
499
     * @param $rawLoad
500
     * @param callable|null $isvalid
501
     * @return array
502
     * @throws InvalidOperationException
503
     */
504
    protected function applyFiltering(
505
        $top,
506
        $skip,
507
        $sourceEntityInstance,
508
        $nullFilter,
509
        $rawLoad,
510
        callable $isvalid = null
511
    ) {
512
        $bulkSetCount = $sourceEntityInstance->count();
513
        $bigSet = 20000 < $bulkSetCount;
514
515
        if ($nullFilter) {
516
            // default no-filter case, palm processing off to database engine - is a lot faster
517
            $resultSet = $sourceEntityInstance->skip($skip)->take($top)->with($rawLoad)->get();
518
            $resultCount = $bulkSetCount;
519
        } elseif ($bigSet) {
520
            if (!(isset($isvalid))) {
521
                $msg = 'Filter closure not set';
522
                throw new InvalidOperationException($msg);
523
            }
524
            $resultSet = new Collection([]);
525
            $rawCount = 0;
526
            $rawTop = null === $top ? $bulkSetCount : $top;
527
528
            // loop thru, chunk by chunk, to reduce chances of exhausting memory
529
            $sourceEntityInstance->chunk(
530
                5000,
531
                function (Collection $results) use ($isvalid, &$skip, &$resultSet, &$rawCount, $rawTop) {
532
                    // apply filter
533
                    $results = $results->filter($isvalid);
534
                    // need to iterate through full result set to find count of items matching filter,
535
                    // so we can't bail out early
536
                    $rawCount += $results->count();
537
                    // now bolt on filtrate to accumulating result set if we haven't accumulated enough bitz
538
                    if ($rawTop > $resultSet->count() + $skip) {
539
                        $resultSet = collect(array_merge($resultSet->all(), $results->all()));
540
                        $sliceAmount = min($skip, $resultSet->count());
541
                        $resultSet = $resultSet->slice($sliceAmount);
542
                        $skip -= $sliceAmount;
543
                    }
544
                }
545
            );
546
547
            // clean up residual to-be-skipped records
548
            $resultSet = $resultSet->slice($skip);
549
            $resultCount = $rawCount;
550
        } else {
551
            if ($sourceEntityInstance instanceof Model) {
552
                /** @var Builder $sourceEntityInstance */
553
                $sourceEntityInstance = $sourceEntityInstance->getQuery();
554
            }
555
            /** @var Collection $resultSet */
556
            $resultSet = $sourceEntityInstance->with($rawLoad)->get();
557
            $resultSet = $resultSet->filter($isvalid);
558
            $resultCount = $resultSet->count();
559
560
            if (isset($skip)) {
561
                $resultSet = $resultSet->slice($skip);
562
            }
563
        }
564
        return [$bulkSetCount, $resultSet, $resultCount, $skip];
565
    }
566
}
567