Completed
Push — master ( ef2609...208b18 )
by Bao
07:27
created

DynamoDbQueryBuilder::orWhere()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 1
1
<?php
2
3
namespace BaoPham\DynamoDb;
4
5
use BaoPham\DynamoDb\Concerns\HasParsers;
6
use BaoPham\DynamoDb\ConditionAnalyzer\Analyzer;
7
use BaoPham\DynamoDb\Facades\DynamoDb;
8
use Closure;
9
use Illuminate\Contracts\Support\Arrayable;
10
use Illuminate\Database\Eloquent\ModelNotFoundException;
11
use Illuminate\Database\Eloquent\Scope;
12
13
class DynamoDbQueryBuilder
14
{
15
    use HasParsers;
16
17
    const MAX_LIMIT = -1;
18
    const DEFAULT_TO_ITERATOR = true;
19
20
    /**
21
     * The maximum number of records to return.
22
     *
23
     * @var int
24
     */
25
    public $limit;
26
27
    /**
28
     * @var array
29
     */
30
    public $wheres = [];
31
32
    /**
33
     * @var DynamoDbModel
34
     */
35
    protected $model;
36
37
    /**
38
     * @var \Aws\DynamoDb\DynamoDbClient
39
     */
40
    protected $client;
41
42
    /**
43
     * @var Closure
44
     */
45
    protected $decorator;
46
47
    /**
48
     * Applied global scopes.
49
     *
50
     * @var array
51
     */
52
    protected $scopes = [];
53
54
    /**
55
     * Removed global scopes.
56
     *
57
     * @var array
58
     */
59
    protected $removedScopes = [];
60
61
    /**
62
     * When not using the iterator, you can store the lastEvaluatedKey to
63
     * paginate through the results. The getAll method will take this into account
64
     * when used with $use_iterator = false.
65
     *
66
     * @var mixed
67
     */
68
    protected $lastEvaluatedKey;
69
70
    /**
71
     * Specified index name for the query.
72
     *
73
     * @var string
74
     */
75
    protected $index;
76
77 115
    public function __construct(DynamoDbModel $model)
78
    {
79 115
        $this->model = $model;
80 115
        $this->client = $model->getClient();
81 115
        $this->setupExpressions();
82 115
    }
83
84
    /**
85
     * Alias to set the "limit" value of the query.
86
     *
87
     * @param  int  $value
88
     * @return DynamoDbQueryBuilder
89
     */
90 6
    public function take($value)
91
    {
92 6
        return $this->limit($value);
93
    }
94
95
    /**
96
     * Set the "limit" value of the query.
97
     *
98
     * @param  int  $value
99
     * @return $this
100
     */
101 10
    public function limit($value)
102
    {
103 10
        $this->limit = $value;
104
105 10
        return $this;
106
    }
107
108
    /**
109
     * Alias to set the "offset" value of the query.
110
     *
111
     * @param  int $value
112
     * @throws NotSupportedException
113
     */
114
    public function skip($value)
115
    {
116
        return $this->offset($value);
117
    }
118
119
    /**
120
     * Set the "offset" value of the query.
121
     *
122
     * @param  int $value
123
     * @throws NotSupportedException
124
     */
125
    public function offset($value)
126
    {
127
        throw new NotSupportedException('Skip/Offset is not supported. Consider using after() instead');
128
    }
129
130
    /**
131
     * Determine the starting point (exclusively) of the query.
132
     * Unfortunately, offset of how many records to skip does not make sense for DynamoDb.
133
     * Instead, provide the last result of the previous query as the starting point for the next query.
134
     *
135
     * @param  DynamoDbModel|null  $after
136
     *   Examples:
137
     *
138
     *   For query such as
139
     *       $query = $model->where('count', 10)->limit(2);
140
     *       $last = $query->all()->last();
141
     *   Take the last item of this query result as the next "offset":
142
     *       $nextPage = $query->after($last)->limit(2)->all();
143
     *
144
     *   Alternatively, pass in nothing to reset the starting point.
145
     *
146
     * @return $this
147
     */
148 4
    public function after(DynamoDbModel $after = null)
149
    {
150 4
        if (empty($after)) {
151 4
            $this->lastEvaluatedKey = null;
152
153 4
            return $this;
154
        }
155
156 4
        $afterKey = $after->getKeys();
157
158 4
        $analyzer = $this->getConditionAnalyzer();
159
160 4
        if ($index = $analyzer->index()) {
161 1
            foreach ($index->columns() as $column) {
162 1
                $afterKey[$column] = $after->getAttribute($column);
163
            }
164
        }
165
166 4
        $this->lastEvaluatedKey = DynamoDb::marshalItem($afterKey);
167
168 4
        return $this;
169
    }
170
171
    /**
172
     * Set the index name manually
173
     *
174
     * @param string $index The index name
175
     * @return $this
176
     */
177 1
    public function withIndex($index)
178
    {
179 1
        $this->index = $index;
180 1
        return $this;
181
    }
182
183 72
    public function where($column, $operator = null, $value = null, $boolean = 'and')
184
    {
185
        // If the column is an array, we will assume it is an array of key-value pairs
186
        // and can add them each as a where clause. We will maintain the boolean we
187
        // received when the method was called and pass it into the nested where.
188 72
        if (is_array($column)) {
189
            foreach ($column as $key => $value) {
190
                return $this->where($key, '=', $value);
191
            }
192
        }
193
194
        // Here we will make some assumptions about the operator. If only 2 values are
195
        // passed to the method, we will assume that the operator is an equals sign
196
        // and keep going. Otherwise, we'll require the operator to be passed in.
197 72
        if (func_num_args() == 2) {
198 43
            list($value, $operator) = [$operator, '='];
199
        }
200
201
        // If the columns is actually a Closure instance, we will assume the developer
202
        // wants to begin a nested where statement which is wrapped in parenthesis.
203
        // We'll add that Closure to the query then return back out immediately.
204 72
        if ($column instanceof Closure) {
205 2
            return $this->whereNested($column, $boolean);
206
        }
207
208
        // If the given operator is not found in the list of valid operators we will
209
        // assume that the developer is just short-cutting the '=' operators and
210
        // we will set the operators to '=' and set the values appropriately.
211 72
        if (!ComparisonOperator::isValidOperator($operator)) {
212 4
            list($value, $operator) = [$operator, '='];
213
        }
214
215
        // If the value is a Closure, it means the developer is performing an entire
216
        // sub-select within the query and we will need to compile the sub-select
217
        // within the where clause to get the appropriate query record results.
218 72
        if ($value instanceof Closure) {
219
            throw new NotSupportedException('Closure in where clause is not supported');
220
        }
221
222 72
        $this->wheres[] = [
223 72
            'column' => $column,
224 72
            'type' => ComparisonOperator::getDynamoDbOperator($operator),
225 72
            'value' => $value,
226 72
            'boolean' => $boolean,
227
        ];
228
229 72
        return $this;
230
    }
231
232
    /**
233
     * Add a nested where statement to the query.
234
     *
235
     * @param  \Closure $callback
236
     * @param  string   $boolean
237
     * @return $this
238
     */
239 2
    public function whereNested(Closure $callback, $boolean = 'and')
240
    {
241 2
        call_user_func($callback, $query = $this->forNestedWhere());
242
243 2
        return $this->addNestedWhereQuery($query, $boolean);
244
    }
245
246
    /**
247
     * Create a new query instance for nested where condition.
248
     *
249
     * @return $this
250
     */
251 2
    public function forNestedWhere()
252
    {
253 2
        return $this->newQuery();
254
    }
255
256
    /**
257
     * Add another query builder as a nested where to the query builder.
258
     *
259
     * @param  DynamoDbQueryBuilder $query
260
     * @param  string  $boolean
261
     * @return $this
262
     */
263 2
    public function addNestedWhereQuery($query, $boolean = 'and')
264
    {
265 2
        if (count($query->wheres)) {
266 2
            $type = 'Nested';
267 2
            $column = null;
268 2
            $value = $query->wheres;
269 2
            $this->wheres[] = compact('column', 'type', 'value', 'boolean');
270
        }
271
272 2
        return $this;
273
    }
274
275
    /**
276
     * Add an "or where" clause to the query.
277
     *
278
     * @param  string  $column
279
     * @param  string  $operator
280
     * @param  mixed   $value
281
     * @return $this
282
     */
283 22
    public function orWhere($column, $operator = null, $value = null)
284
    {
285 22
        return $this->where($column, $operator, $value, 'or');
286
    }
287
288
    /**
289
     * Add a "where in" clause to the query.
290
     *
291
     * @param  string  $column
292
     * @param  mixed   $values
293
     * @param  string  $boolean
294
     * @param  bool    $not
295
     * @return $this
296
     * @throws NotSupportedException
297
     */
298 2
    public function whereIn($column, $values, $boolean = 'and', $not = false)
299
    {
300 2
        if ($not) {
301
            throw new NotSupportedException('"not in" is not a valid DynamoDB comparison operator');
302
        }
303
304
        // If the value is a query builder instance, not supported
305 2
        if ($values instanceof static) {
306
            throw new NotSupportedException('Value is a query builder instance');
307
        }
308
309
        // If the value of the where in clause is actually a Closure, not supported
310 2
        if ($values instanceof Closure) {
311
            throw new NotSupportedException('Value is a Closure');
312
        }
313
314
        // Next, if the value is Arrayable we need to cast it to its raw array form
315 2
        if ($values instanceof Arrayable) {
316
            $values = $values->toArray();
317
        }
318
319 2
        return $this->where($column, ComparisonOperator::IN, $values, $boolean);
320
    }
321
322
    /**
323
     * Add an "or where in" clause to the query.
324
     *
325
     * @param  string  $column
326
     * @param  mixed   $values
327
     * @return $this
328
     */
329 2
    public function orWhereIn($column, $values)
330
    {
331 2
        return $this->whereIn($column, $values, 'or');
332
    }
333
334
    /**
335
     * Add a "where null" clause to the query.
336
     *
337
     * @param  string  $column
338
     * @param  string  $boolean
339
     * @param  bool    $not
340
     * @return $this
341
     */
342 4
    public function whereNull($column, $boolean = 'and', $not = false)
343
    {
344 4
        $type = $not ? ComparisonOperator::NOT_NULL : ComparisonOperator::NULL;
345
346 4
        $this->wheres[] = compact('column', 'type', 'boolean');
347
348 4
        return $this;
349
    }
350
351
    /**
352
     * Add an "or where null" clause to the query.
353
     *
354
     * @param  string  $column
355
     * @return $this
356
     */
357 2
    public function orWhereNull($column)
358
    {
359 2
        return $this->whereNull($column, 'or');
360
    }
361
362
    /**
363
     * Add an "or where not null" clause to the query.
364
     *
365
     * @param  string  $column
366
     * @return $this
367
     */
368 2
    public function orWhereNotNull($column)
369
    {
370 2
        return $this->whereNotNull($column, 'or');
371
    }
372
373
    /**
374
     * Add a "where not null" clause to the query.
375
     *
376
     * @param  string  $column
377
     * @param  string  $boolean
378
     * @return $this
379
     */
380 2
    public function whereNotNull($column, $boolean = 'and')
381
    {
382 2
        return $this->whereNull($column, $boolean, true);
383
    }
384
385
    /**
386
     * Get a new instance of the query builder.
387
     *
388
     * @return DynamoDbQueryBuilder
389
     */
390 2
    public function newQuery()
391
    {
392 2
        return new static($this->getModel());
393
    }
394
395
    /**
396
     * Implements the Query Chunk method
397
     *
398
     * @param int $chunkSize
399
     * @param callable $callback
400
     */
401 7
    public function chunk($chunkSize, callable $callback)
402
    {
403 7
        while (true) {
404 7
            $results = $this->getAll([], $chunkSize, false);
405
406 7
            if ($results->isNotEmpty()) {
407 7
                call_user_func($callback, $results);
408
            }
409
410 7
            if (empty($this->lastEvaluatedKey)) {
411 7
                break;
412
            }
413
        }
414 7
    }
415
416
    /**
417
     * @param $id
418
     * @param array $columns
419
     * @return DynamoDbModel|\Illuminate\Database\Eloquent\Collection|null
420
     */
421 34
    public function find($id, array $columns = [])
422
    {
423 34
        if ($this->isMultipleIds($id)) {
424 4
            return $this->findMany($id, $columns);
425
        }
426
427 30
        $this->resetExpressions();
428
429 30
        $this->model->setId($id);
430
431 30
        $query = DynamoDb::table($this->model->getTable())
432 30
            ->setKey(DynamoDb::marshalItem($this->model->getKeys()))
433 30
            ->setConsistentRead(true);
434
435 30
        if (!empty($columns)) {
436
            $query
437 3
                ->setProjectionExpression($this->projectionExpression->parse($columns))
438 3
                ->setExpressionAttributeNames($this->expressionAttributeNames->all());
439
        }
440
441 30
        $item = $query->prepare($this->client)->getItem();
442
443 30
        $item = array_get($item->toArray(), 'Item');
444
445 30
        if (empty($item)) {
446 4
            return null;
447
        }
448
449 26
        $item = DynamoDb::unmarshalItem($item);
450
451 26
        $model = $this->model->newInstance([], true);
452
453 26
        $model->setRawAttributes($item, true);
454
455 26
        return $model;
456
    }
457
458
    /**
459
     * @param $ids
460
     * @param array $columns
461
     * @return \Illuminate\Database\Eloquent\Collection
462
     */
463 4
    public function findMany($ids, array $columns = [])
464
    {
465 4
        $collection = $this->model->newCollection();
466
467 4
        if (empty($ids)) {
468
            return $collection;
469
        }
470
471 4
        $this->resetExpressions();
472
473 4
        $table = $this->model->getTable();
474
475
        $keys = collect($ids)->map(function ($id) {
476 4
            if (! is_array($id)) {
477 2
                $id = [$this->model->getKeyName() => $id];
478
            }
479
480 4
            return DynamoDb::marshalItem($id);
481 4
        });
482
483 4
        $subQuery = DynamoDb::newQuery()
484 4
            ->setKeys($keys->toArray())
485 4
            ->setProjectionExpression($this->projectionExpression->parse($columns))
486 4
            ->setExpressionAttributeNames($this->expressionAttributeNames->all())
487 4
            ->prepare($this->client)
488 4
            ->query;
489
490 4
        $results = DynamoDb::newQuery()
491 4
            ->setRequestItems([$table => $subQuery])
492 4
            ->prepare($this->client)
493 4
            ->batchGetItem();
494
495 4
        foreach ($results['Responses'][$table] as $item) {
496 4
            $item = DynamoDb::unmarshalItem($item);
497 4
            $model = $this->model->newInstance([], true);
498 4
            $model->setRawAttributes($item, true);
499 4
            $collection->add($model);
500
        }
501
502 4
        return $collection;
503
    }
504
505 5
    public function findOrFail($id, $columns = [])
506
    {
507 5
        $result = $this->find($id, $columns);
508
509 5
        if ($this->isMultipleIds($id)) {
510 1
            if (count($result) == count(array_unique($id))) {
511 1
                return $result;
512
            }
513 4
        } elseif (! is_null($result)) {
514 2
            return $result;
515
        }
516
517 2
        throw (new ModelNotFoundException)->setModel(
518 2
            get_class($this->model),
519 2
            $id
520
        );
521
    }
522
523 16
    public function first($columns = [])
524
    {
525 16
        $items = $this->getAll($columns, 1);
526
527 16
        return $items->first();
528
    }
529
530 6
    public function firstOrFail($columns = [])
531
    {
532 6
        if (! is_null($model = $this->first($columns))) {
533 4
            return $model;
534
        }
535
536 2
        throw (new ModelNotFoundException)->setModel(get_class($this->model));
537
    }
538
539
    /**
540
     * Remove attributes from an existing item
541
     *
542
     * @param array ...$attributes
543
     * @return bool
544
     * @throws InvalidQuery
545
     */
546 6
    public function removeAttribute(...$attributes)
547
    {
548 6
        $keySet = !empty(array_filter($this->model->getKeys()));
549
550 6
        if (!$keySet) {
551 4
            $analyzer = $this->getConditionAnalyzer();
552
553 4
            if (!$analyzer->isExactSearch()) {
554
                throw new InvalidQuery('Need to provide the key in your query');
555
            }
556
557 4
            $id = $analyzer->identifierConditionValues();
558 4
            $this->model->setId($id);
559
        }
560
561 6
        $key = DynamoDb::marshalItem($this->model->getKeys());
562
563 6
        $this->resetExpressions();
564
565 6
        $result = DynamoDb::table($this->model->getTable())
566 6
            ->setKey($key)
567 6
            ->setUpdateExpression($this->updateExpression->remove($attributes))
568 6
            ->setExpressionAttributeNames($this->expressionAttributeNames->all())
569 6
            ->prepare($this->client)
570 6
            ->updateItem();
571
572 6
        return array_get($result, '@metadata.statusCode') === 200;
573
    }
574
575 55
    public function get($columns = [])
576
    {
577 55
        return $this->all($columns);
578
    }
579
580 2
    public function delete()
581
    {
582 2
        $result = DynamoDb::table($this->model->getTable())
583 2
            ->setKey(DynamoDb::marshalItem($this->model->getKeys()))
584 2
            ->prepare($this->client)
585 2
            ->deleteItem();
586
587 2
        return array_get($result->toArray(), '@metadata.statusCode') === 200;
588
    }
589
590 8
    public function save()
591
    {
592 8
        $result = DynamoDb::table($this->model->getTable())
593 8
            ->setItem(DynamoDb::marshalItem($this->model->getAttributes()))
594 8
            ->prepare($this->client)
595 8
            ->putItem();
596
597 8
        return array_get($result, '@metadata.statusCode') === 200;
598
    }
599
600 63
    public function all($columns = [])
601
    {
602 63
        $limit = isset($this->limit) ? $this->limit : static::MAX_LIMIT;
603 63
        return $this->getAll($columns, $limit);
604
    }
605
606 4
    public function count()
607
    {
608 4
        $limit = isset($this->limit) ? $this->limit : static::MAX_LIMIT;
609 4
        $raw = $this->toDynamoDbQuery(['count(*)'], $limit);
610
611 4
        if ($raw->op === 'Scan') {
612 4
            $res = $this->client->scan($raw->query);
613
        } else {
614
            $res = $this->client->query($raw->query);
615
        }
616
617 4
        return $res['Count'];
618
    }
619
620 4
    public function decorate(Closure $closure)
621
    {
622 4
        $this->decorator = $closure;
623 4
        return $this;
624
    }
625
626 82
    protected function getAll(
627
        $columns = [],
628
        $limit = DynamoDbQueryBuilder::MAX_LIMIT,
629
        $useIterator = DynamoDbQueryBuilder::DEFAULT_TO_ITERATOR
630
    ) {
631 82
        $analyzer = $this->getConditionAnalyzer();
632
633 82
        if ($analyzer->isExactSearch()) {
634 7
            $item = $this->find($analyzer->identifierConditionValues(), $columns);
635
636 7
            return $this->getModel()->newCollection([$item]);
637
        }
638
639 76
        $raw = $this->toDynamoDbQuery($columns, $limit);
640
641 76
        if ($useIterator) {
642 69
            $iterator = $this->client->getIterator($raw->op, $raw->query);
643
644 69
            if (isset($raw->query['Limit'])) {
645 18
                $iterator = new \LimitIterator($iterator, 0, $raw->query['Limit']);
646
            }
647
        } else {
648 7
            if ($raw->op === 'Scan') {
649 7
                $res = $this->client->scan($raw->query);
650
            } else {
651
                $res = $this->client->query($raw->query);
652
            }
653
654 7
            $this->lastEvaluatedKey = array_get($res, 'LastEvaluatedKey');
655 7
            $iterator = $res['Items'];
656
        }
657
658 76
        $results = [];
659
660 76
        foreach ($iterator as $item) {
661 76
            $item = DynamoDb::unmarshalItem($item);
662 76
            $model = $this->model->newInstance([], true);
663 76
            $model->setRawAttributes($item, true);
664 76
            $results[] = $model;
665
        }
666
667 76
        return $this->getModel()->newCollection($results);
668
    }
669
670
    /**
671
     * Return the raw DynamoDb query
672
     *
673
     * @param array $columns
674
     * @param int $limit
675
     * @return RawDynamoDbQuery
676
     */
677 86
    public function toDynamoDbQuery(
678
        $columns = [],
679
        $limit = DynamoDbQueryBuilder::MAX_LIMIT
680
    ) {
681 86
        $this->applyScopes();
682
683 86
        $this->resetExpressions();
684
685 86
        $op = 'Scan';
686 86
        $queryBuilder = DynamoDb::table($this->model->getTable());
687
688 86
        if (! empty($this->wheres)) {
689 68
            $analyzer = $this->getConditionAnalyzer();
690
691 68
            if ($keyConditions = $analyzer->keyConditions()) {
692 14
                $op = 'Query';
693 14
                $queryBuilder->setKeyConditionExpression($this->keyConditionExpression->parse($keyConditions));
694
            }
695
696 68
            if ($filterConditions = $analyzer->filterConditions()) {
697 61
                $queryBuilder->setFilterExpression($this->filterExpression->parse($filterConditions));
698
            }
699
700 68
            if ($index = $analyzer->index()) {
701 7
                $queryBuilder->setIndexName($index->name);
702
            }
703
        }
704
705 86
        if ($this->index) {
706
            // If user specifies the index manually, respect that
707 1
            $queryBuilder->setIndexName($this->index);
708
        }
709
710 86
        if ($limit !== static::MAX_LIMIT) {
711 27
            $queryBuilder->setLimit($limit);
712
        }
713
714 86
        if (!empty($columns)) {
715
            // Either we try to get the count or specific columns
716 8
            if ($columns == ['count(*)']) {
717 6
                $queryBuilder->setSelect('COUNT');
718
            } else {
719 2
                $queryBuilder->setProjectionExpression($this->projectionExpression->parse($columns));
720
            }
721
        }
722
723 86
        if (!empty($this->lastEvaluatedKey)) {
724 11
            $queryBuilder->setExclusiveStartKey($this->lastEvaluatedKey);
725
        }
726
727
        $queryBuilder
728 86
            ->setExpressionAttributeNames($this->expressionAttributeNames->all())
729 86
            ->setExpressionAttributeValues($this->expressionAttributeValues->all());
730
731 86
        $raw = new RawDynamoDbQuery($op, $queryBuilder->prepare($this->client)->query);
732
733 86
        if ($this->decorator) {
734 4
            call_user_func($this->decorator, $raw);
735
        }
736
737 86
        return $raw;
738
    }
739
740
    /**
741
     * @return Analyzer
742
     */
743 91
    protected function getConditionAnalyzer()
744
    {
745 91
        return with(new Analyzer)
746 91
            ->on($this->model)
747 91
            ->withIndex($this->index)
748 91
            ->analyze($this->wheres);
749
    }
750
751 34
    protected function isMultipleIds($id)
752
    {
753 34
        $keys = collect($this->model->getKeyNames());
754
755
        // could be ['id' => 'foo'], ['id1' => 'foo', 'id2' => 'bar']
756
        $single = $keys->first(function ($name) use ($id) {
757 34
            return !isset($id[$name]);
758 34
        }) === null;
759
760 34
        if ($single) {
761 19
            return false;
762
        }
763
764
        // could be ['foo', 'bar'], [['id1' => 'foo', 'id2' => 'bar'], ...]
765 15
        return $this->model->hasCompositeKey() ? is_array(array_first($id)) : is_array($id);
766
    }
767
768
    /**
769
     * @return DynamoDbModel
770
     */
771 82
    public function getModel()
772
    {
773 82
        return $this->model;
774
    }
775
776
    /**
777
     * @return \Aws\DynamoDb\DynamoDbClient
778
     */
779
    public function getClient()
780
    {
781
        return $this->client;
782
    }
783
784
    /**
785
     * Register a new global scope.
786
     *
787
     * @param  string  $identifier
788
     * @param  \Illuminate\Database\Eloquent\Scope|\Closure  $scope
789
     * @return $this
790
     */
791 7
    public function withGlobalScope($identifier, $scope)
792
    {
793 7
        $this->scopes[$identifier] = $scope;
794
795 7
        if (method_exists($scope, 'extend')) {
796
            $scope->extend($this);
797
        }
798
799 7
        return $this;
800
    }
801
802
    /**
803
     * Remove a registered global scope.
804
     *
805
     * @param  \Illuminate\Database\Eloquent\Scope|string  $scope
806
     * @return $this
807
     */
808 3
    public function withoutGlobalScope($scope)
809
    {
810 3
        if (! is_string($scope)) {
811
            $scope = get_class($scope);
812
        }
813
814 3
        unset($this->scopes[$scope]);
815
816 3
        $this->removedScopes[] = $scope;
817
818 3
        return $this;
819
    }
820
821
    /**
822
     * Remove all or passed registered global scopes.
823
     *
824
     * @param  array|null  $scopes
825
     * @return $this
826
     */
827 5
    public function withoutGlobalScopes(array $scopes = null)
828
    {
829 5
        if (is_array($scopes)) {
830
            foreach ($scopes as $scope) {
831
                $this->withoutGlobalScope($scope);
832
            }
833
        } else {
834 5
            $this->scopes = [];
835
        }
836
837 5
        return $this;
838
    }
839
840
    /**
841
     * Get an array of global scopes that were removed from the query.
842
     *
843
     * @return array
844
     */
845
    public function removedScopes()
846
    {
847
        return $this->removedScopes;
848
    }
849
850
    /**
851
     * Apply the scopes to the Eloquent builder instance and return it.
852
     *
853
     * @return DynamoDbQueryBuilder
854
     */
855 86
    public function applyScopes()
856
    {
857 86
        if (! $this->scopes) {
858 85
            return $this;
859
        }
860
861 3
        $builder = $this;
862
863 3
        foreach ($builder->scopes as $identifier => $scope) {
864 3
            if (! isset($builder->scopes[$identifier])) {
865
                continue;
866
            }
867
868
            $builder->callScope(function (DynamoDbQueryBuilder $builder) use ($scope) {
869
                // If the scope is a Closure we will just go ahead and call the scope with the
870
                // builder instance. The "callScope" method will properly group the clauses
871
                // that are added to this query so "where" clauses maintain proper logic.
872 3
                if ($scope instanceof Closure) {
873 3
                    $scope($builder);
874
                }
875
876
                // If the scope is a scope object, we will call the apply method on this scope
877
                // passing in the builder and the model instance. After we run all of these
878
                // scopes we will return back the builder instance to the outside caller.
879 3
                if ($scope instanceof Scope) {
880
                    throw new NotSupportedException('Scope object is not yet supported');
881
                }
882 3
            });
883
884 3
            $builder->withoutGlobalScope($identifier);
885
        }
886
887 3
        return $builder;
888
    }
889
890
    /**
891
     * Apply the given scope on the current builder instance.
892
     *
893
     * @param  callable  $scope
894
     * @param  array  $parameters
895
     * @return mixed
896
     */
897 7
    protected function callScope(callable $scope, $parameters = [])
898
    {
899 7
        array_unshift($parameters, $this);
900
901
        // $query = $this->getQuery();
902
903
        // // We will keep track of how many wheres are on the query before running the
904
        // // scope so that we can properly group the added scope constraints in the
905
        // // query as their own isolated nested where statement and avoid issues.
906
        // $originalWhereCount = is_null($query->wheres)
907
        //             ? 0 : count($query->wheres);
908
909 7
        $result = $scope(...array_values($parameters)) ?: $this;
910
911
        // if (count((array) $query->wheres) > $originalWhereCount) {
912
        //     $this->addNewWheresWithinGroup($query, $originalWhereCount);
913
        // }
914
915 7
        return $result;
916
    }
917
918
    /**
919
     * Dynamically handle calls into the query instance.
920
     *
921
     * @param  string  $method
922
     * @param  array  $parameters
923
     * @return mixed
924
     */
925 7
    public function __call($method, $parameters)
926
    {
927 7
        if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
928 5
            return $this->callScope([$this->model, $scope], $parameters);
929
        }
930
931 2
        return $this;
932
    }
933
}
934