Passed
Pull Request — master (#142)
by Bao
07:46
created

DynamoDbQueryBuilder::toDynamoDbQuery()   D

Complexity

Conditions 11
Paths 432

Size

Total Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 61
rs 4.002
c 0
b 0
f 0
cc 11
nc 432
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Aws\DynamoDb\DynamoDbClient;
10
use Illuminate\Contracts\Support\Arrayable;
11
use Illuminate\Database\Eloquent\Collection;
12
use \Illuminate\Database\Eloquent\ModelNotFoundException;
13
use Illuminate\Database\Eloquent\Scope;
14
15
class DynamoDbQueryBuilder
16
{
17
    use HasParsers;
18
19
    const MAX_LIMIT = -1;
20
    const DEFAULT_TO_ITERATOR = true;
21
22
    /**
23
     * The maximum number of records to return.
24
     *
25
     * @var int
26
     */
27
    public $limit;
28
29
    /**
30
     * @var array
31
     */
32
    public $wheres = [];
33
34
    /**
35
     * @var DynamoDbModel
36
     */
37
    protected $model;
38
39
    /**
40
     * @var DynamoDbClient
41
     */
42
    protected $client;
43
44
    /**
45
     * @var Closure
46
     */
47
    protected $decorator;
48
49
    /**
50
     * Applied global scopes.
51
     *
52
     * @var array
53
     */
54
    protected $scopes = [];
55
56
    /**
57
     * Removed global scopes.
58
     *
59
     * @var array
60
     */
61
    protected $removedScopes = [];
62
63
    /**
64
     * When not using the iterator, you can store the lastEvaluatedKey to
65
     * paginate through the results. The getAll method will take this into account
66
     * when used with $use_iterator = false.
67
     *
68
     * @var mixed
69
     */
70
    protected $lastEvaluatedKey;
71
72
    /**
73
     * Specified index name for the query.
74
     *
75
     * @var string
76
     */
77
    protected $index;
78
79
    public function __construct(DynamoDbModel $model)
80
    {
81
        $this->model = $model;
82
        $this->client = $model->getClient();
83
        $this->setupExpressions();
84
    }
85
86
    /**
87
     * Alias to set the "limit" value of the query.
88
     *
89
     * @param  int  $value
90
     * @return DynamoDbQueryBuilder
91
     */
92
    public function take($value)
93
    {
94
        return $this->limit($value);
95
    }
96
97
    /**
98
     * Set the "limit" value of the query.
99
     *
100
     * @param  int  $value
101
     * @return $this
102
     */
103
    public function limit($value)
104
    {
105
        $this->limit = $value;
106
107
        return $this;
108
    }
109
110
    /**
111
     * Alias to set the "offset" value of the query.
112
     *
113
     * @param  int $value
114
     * @throws NotSupportedException
115
     */
116
    public function skip($value)
117
    {
118
        return $this->offset($value);
119
    }
120
121
    /**
122
     * Set the "offset" value of the query.
123
     *
124
     * @param  int $value
125
     * @throws NotSupportedException
126
     */
127
    public function offset($value)
1 ignored issue
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

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

127
    public function offset(/** @scrutinizer ignore-unused */ $value)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
128
    {
129
        throw new NotSupportedException('Skip/Offset is not supported. Consider using after() instead');
130
    }
131
132
    /**
133
     * Determine the starting point (exclusively) of the query.
134
     * Unfortunately, offset of how many records to skip does not make sense for DynamoDb.
135
     * Instead, provide the last result of the previous query as the starting point for the next query.
136
     *
137
     * @param  DynamoDbModel|null  $after
138
     *   Examples:
139
     *
140
     *   For query such as
141
     *       $query = $model->where('count', 10)->limit(2);
142
     *       $last = $query->all()->last();
143
     *   Take the last item of this query result as the next "offset":
144
     *       $nextPage = $query->after($last)->limit(2)->all();
145
     *
146
     *   Alternatively, pass in nothing to reset the starting point.
147
     *
148
     * @return $this
149
     */
150
    public function after(DynamoDbModel $after = null)
151
    {
152
        if (empty($after)) {
153
            $this->lastEvaluatedKey = null;
154
155
            return $this;
156
        }
157
158
        $afterKey = $after->getKeys();
159
160
        $analyzer = $this->getConditionAnalyzer();
161
162
        if ($index = $analyzer->index()) {
163
            foreach ($index->columns() as $column) {
164
                $afterKey[$column] = $after->getAttribute($column);
165
            }
166
        }
167
168
        $this->lastEvaluatedKey = $this->getDynamoDbKey($afterKey);
169
170
        return $this;
171
    }
172
173
    /**
174
     * Set the index name manually
175
     *
176
     * @param string $index The index name
177
     * @return $this
178
     */
179
    public function withIndex($index)
180
    {
181
        $this->index = $index;
182
        return $this;
183
    }
184
185
    public function where($column, $operator = null, $value = null, $boolean = 'and')
186
    {
187
        // If the column is an array, we will assume it is an array of key-value pairs
188
        // and can add them each as a where clause. We will maintain the boolean we
189
        // received when the method was called and pass it into the nested where.
190
        if (is_array($column)) {
191
            foreach ($column as $key => $value) {
192
                return $this->where($key, '=', $value);
193
            }
194
        }
195
196
        // Here we will make some assumptions about the operator. If only 2 values are
197
        // passed to the method, we will assume that the operator is an equals sign
198
        // and keep going. Otherwise, we'll require the operator to be passed in.
199
        if (func_num_args() == 2) {
200
            list($value, $operator) = [$operator, '='];
201
        }
202
203
        // If the columns is actually a Closure instance, we will assume the developer
204
        // wants to begin a nested where statement which is wrapped in parenthesis.
205
        // We'll add that Closure to the query then return back out immediately.
206
        if ($column instanceof Closure) {
207
            return $this->whereNested($column, $boolean);
208
        }
209
210
        // If the given operator is not found in the list of valid operators we will
211
        // assume that the developer is just short-cutting the '=' operators and
212
        // we will set the operators to '=' and set the values appropriately.
213
        if (!ComparisonOperator::isValidOperator($operator)) {
214
            list($value, $operator) = [$operator, '='];
215
        }
216
217
        // If the value is a Closure, it means the developer is performing an entire
218
        // sub-select within the query and we will need to compile the sub-select
219
        // within the where clause to get the appropriate query record results.
220
        if ($value instanceof Closure) {
221
            throw new NotSupportedException('Closure in where clause is not supported');
222
        }
223
224
        $this->wheres[] = [
225
            'column' => $column,
226
            'type' => ComparisonOperator::getDynamoDbOperator($operator),
227
            'value' => $value,
228
            'boolean' => $boolean,
229
        ];
230
231
        return $this;
232
    }
233
234
    /**
235
     * Add a nested where statement to the query.
236
     *
237
     * @param  \Closure $callback
238
     * @param  string   $boolean
239
     * @return $this
240
     */
241
    public function whereNested(Closure $callback, $boolean = 'and')
242
    {
243
        call_user_func($callback, $query = $this->forNestedWhere());
244
245
        return $this->addNestedWhereQuery($query, $boolean);
246
    }
247
248
    /**
249
     * Create a new query instance for nested where condition.
250
     *
251
     * @return $this
252
     */
253
    public function forNestedWhere()
254
    {
255
        return $this->newQuery();
256
    }
257
258
    /**
259
     * Add another query builder as a nested where to the query builder.
260
     *
261
     * @param  DynamoDbQueryBuilder $query
262
     * @param  string  $boolean
263
     * @return $this
264
     */
265
    public function addNestedWhereQuery($query, $boolean = 'and')
266
    {
267
        if (count($query->wheres)) {
268
            $type = 'Nested';
269
            $column = null;
270
            $value = $query->wheres;
271
            $this->wheres[] = compact('column', 'type', 'value', 'boolean');
272
        }
273
274
        return $this;
275
    }
276
277
    /**
278
     * Add an "or where" clause to the query.
279
     *
280
     * @param  string  $column
281
     * @param  string  $operator
282
     * @param  mixed   $value
283
     * @return $this
284
     */
285
    public function orWhere($column, $operator = null, $value = null)
286
    {
287
        return $this->where($column, $operator, $value, 'or');
288
    }
289
290
    /**
291
     * Add a "where in" clause to the query.
292
     *
293
     * @param  string  $column
294
     * @param  mixed   $values
295
     * @param  string  $boolean
296
     * @param  bool    $not
297
     * @return $this
298
     * @throws NotSupportedException
299
     */
300
    public function whereIn($column, $values, $boolean = 'and', $not = false)
301
    {
302
        if ($not) {
303
            throw new NotSupportedException('"not in" is not a valid DynamoDB comparison operator');
304
        }
305
306
        // If the value is a query builder instance, not supported
307
        if ($values instanceof static) {
308
            throw new NotSupportedException('Value is a query builder instance');
309
        }
310
311
        // If the value of the where in clause is actually a Closure, not supported
312
        if ($values instanceof Closure) {
313
            throw new NotSupportedException('Value is a Closure');
314
        }
315
316
        // Next, if the value is Arrayable we need to cast it to its raw array form
317
        if ($values instanceof Arrayable) {
318
            $values = $values->toArray();
319
        }
320
321
        return $this->where($column, ComparisonOperator::IN, $values, $boolean);
322
    }
323
324
    /**
325
     * Add an "or where in" clause to the query.
326
     *
327
     * @param  string  $column
328
     * @param  mixed   $values
329
     * @return $this
330
     */
331
    public function orWhereIn($column, $values)
332
    {
333
        return $this->whereIn($column, $values, 'or');
334
    }
335
336
    /**
337
     * Add a "where null" clause to the query.
338
     *
339
     * @param  string  $column
340
     * @param  string  $boolean
341
     * @param  bool    $not
342
     * @return $this
343
     */
344
    public function whereNull($column, $boolean = 'and', $not = false)
345
    {
346
        $type = $not ? ComparisonOperator::NOT_NULL : ComparisonOperator::NULL;
347
348
        $this->wheres[] = compact('column', 'type', 'boolean');
349
350
        return $this;
351
    }
352
353
    /**
354
     * Add an "or where null" clause to the query.
355
     *
356
     * @param  string  $column
357
     * @return $this
358
     */
359
    public function orWhereNull($column)
360
    {
361
        return $this->whereNull($column, 'or');
362
    }
363
364
    /**
365
     * Add an "or where not null" clause to the query.
366
     *
367
     * @param  string  $column
368
     * @return $this
369
     */
370
    public function orWhereNotNull($column)
371
    {
372
        return $this->whereNotNull($column, 'or');
373
    }
374
375
    /**
376
     * Add a "where not null" clause to the query.
377
     *
378
     * @param  string  $column
379
     * @param  string  $boolean
380
     * @return $this
381
     */
382
    public function whereNotNull($column, $boolean = 'and')
383
    {
384
        return $this->whereNull($column, $boolean, true);
385
    }
386
387
    /**
388
     * Get a new instance of the query builder.
389
     *
390
     * @return DynamoDbQueryBuilder
391
     */
392
    public function newQuery()
393
    {
394
        return new static($this->getModel());
395
    }
396
397
    /**
398
     * Implements the Query Chunk method
399
     *
400
     * @param int $chunkSize
401
     * @param callable $callback
402
     */
403
    public function chunk($chunkSize, callable $callback)
404
    {
405
        while (true) {
406
            $results = $this->getAll([], $chunkSize, false);
407
408
            if ($results->isNotEmpty()) {
409
                call_user_func($callback, $results);
410
            }
411
412
            if (empty($this->lastEvaluatedKey)) {
413
                break;
414
            }
415
        }
416
    }
417
418
    /**
419
     * @param $id
420
     * @param array $columns
421
     * @return DynamoDbModel|Collection|null
422
     */
423
    public function find($id, array $columns = [])
424
    {
425
        if ($this->isMultipleIds($id)) {
426
            return $this->findMany($id, $columns);
427
        }
428
429
        $this->resetExpressions();
430
431
        $this->model->setId($id);
432
433
        $query = DynamoDb::table($this->model->getTable())
434
            ->setKey($this->getDynamoDbKey())
435
            ->setConsistentRead(true);
0 ignored issues
show
Bug introduced by
The method setConsistentRead() does not exist on BaoPham\DynamoDb\DynamoDb\QueryBuilder. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

435
            ->/** @scrutinizer ignore-call */ setConsistentRead(true);
Loading history...
436
437
        if (!empty($columns)) {
438
            $query
439
                ->setProjectionExpression($this->projectionExpression->parse($columns))
440
                ->setExpressionAttributeNames($this->expressionAttributeNames->all());
441
        }
442
443
        $item = $query->prepare($this->client)->getItem();
444
445
        $item = array_get($item->toArray(), 'Item');
446
447
        if (empty($item)) {
448
            return null;
449
        }
450
451
        $item = DynamoDb::unmarshalItem($item);
452
453
        $model = $this->model->newInstance([], true);
454
455
        $model->setRawAttributes($item, true);
456
457
        return $model;
458
    }
459
460
    /**
461
     * @param $ids
462
     * @param array $columns
463
     * @return Collection
464
     */
465
    public function findMany($ids, array $columns = [])
466
    {
467
        $collection = $this->model->newCollection();
468
469
        if (empty($ids)) {
470
            return $collection;
471
        }
472
473
        $this->resetExpressions();
474
475
        $table = $this->model->getTable();
476
477
        $keys = collect($ids)->map(function ($id) {
478
            if (! is_array($id)) {
479
                $id = [$this->model->getKeyName() => $id];
480
            }
481
482
            return DynamoDb::marshalItem($id);
483
        });
484
485
        $subQuery = DynamoDb::newQuery()
486
            ->setKeys($keys->toArray())
487
            ->setProjectionExpression($this->projectionExpression->parse($columns))
488
            ->setExpressionAttributeNames($this->expressionAttributeNames->all())
489
            ->prepare($this->client)
490
            ->query;
491
492
        $results = DynamoDb::newQuery()
493
            ->setRequestItems([$table => $subQuery])
494
            ->prepare($this->client)
495
            ->batchGetItem();
496
497
        foreach ($results['Responses'][$table] as $item) {
498
            $item = DynamoDb::unmarshalItem($item);
499
            $model = $this->model->newInstance([], true);
500
            $model->setRawAttributes($item, true);
501
            $collection->add($model);
502
        }
503
504
        return $collection;
505
    }
506
507
    public function findOrFail($id, $columns = [])
508
    {
509
        $result = $this->find($id, $columns);
510
511
        if ($this->isMultipleIds($id)) {
512
            if (count($result) == count(array_unique($id))) {
1 ignored issue
show
Bug introduced by
It seems like $result can also be of type BaoPham\DynamoDb\DynamoDbModel; however, parameter $var of count() does only seem to accept Countable|array, 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

512
            if (count(/** @scrutinizer ignore-type */ $result) == count(array_unique($id))) {
Loading history...
513
                return $result;
514
            }
515
        } elseif (! is_null($result)) {
516
            return $result;
517
        }
518
519
        throw (new ModelNotFoundException)->setModel(
520
            get_class($this->model),
521
            $id
522
        );
523
    }
524
525
    public function first($columns = [])
526
    {
527
        $items = $this->getAll($columns, 1);
528
529
        return $items->first();
530
    }
531
532
    public function firstOrFail($columns = [])
533
    {
534
        if (! is_null($model = $this->first($columns))) {
535
            return $model;
536
        }
537
538
        throw (new ModelNotFoundException)->setModel(get_class($this->model));
539
    }
540
541
    /**
542
     * Remove attributes from an existing item
543
     *
544
     * @param array ...$attributes
545
     * @return bool
546
     * @throws InvalidQuery
547
     */
548
    public function removeAttribute(...$attributes)
549
    {
550
        $key = $this->getDynamoDbKey();
551
552
        if (empty($key)) {
553
            $analyzer = $this->getConditionAnalyzer();
554
555
            if (!$analyzer->isExactSearch()) {
556
                throw new InvalidQuery('Need to provide the key in your query');
557
            }
558
559
            $id = $analyzer->identifierConditionValues();
560
            $this->model->setId($id);
561
            $key = $this->getDynamoDbKey();
562
        }
563
564
        $this->resetExpressions();
565
566
        $result = DynamoDb::table($this->model->getTable())
567
            ->setKey($key)
568
            ->setUpdateExpression($this->updateExpression->remove($attributes))
569
            ->setExpressionAttributeNames($this->expressionAttributeNames->all())
570
            ->prepare($this->client)
571
            ->updateItem();
572
573
        return array_get($result, '@metadata.statusCode') === 200;
574
    }
575
576
    public function get($columns = [])
577
    {
578
        return $this->all($columns);
579
    }
580
581
    public function delete()
582
    {
583
        $result = DynamoDb::table($this->model->getTable())
584
            ->setKey($this->getDynamoDbKey())
585
            ->prepare($this->client)
586
            ->deleteItem();
587
588
        return array_get($result->toArray(), '@metadata.statusCode') === 200;
589
    }
590
591
    public function save()
592
    {
593
        $result = DynamoDb::table($this->model->getTable())
594
            ->setItem(DynamoDb::marshalItem($this->model->getAttributes()))
595
            ->prepare($this->client)
596
            ->putItem();
597
598
        return array_get($result, '@metadata.statusCode') === 200;
599
    }
600
601
    public function all($columns = [])
602
    {
603
        $limit = isset($this->limit) ? $this->limit : static::MAX_LIMIT;
604
        return $this->getAll($columns, $limit);
605
    }
606
607
    public function count()
608
    {
609
        $limit = isset($this->limit) ? $this->limit : static::MAX_LIMIT;
610
        $raw = $this->toDynamoDbQuery(['count(*)'], $limit);
611
612
        if ($raw->op === 'Scan') {
613
            $res = $this->client->scan($raw->query);
614
        } else {
615
            $res = $this->client->query($raw->query);
616
        }
617
618
        return $res['Count'];
619
    }
620
621
    public function decorate(Closure $closure)
622
    {
623
        $this->decorator = $closure;
624
        return $this;
625
    }
626
627
    protected function getAll(
628
        $columns = [],
629
        $limit = DynamoDbQueryBuilder::MAX_LIMIT,
630
        $useIterator = DynamoDbQueryBuilder::DEFAULT_TO_ITERATOR
631
    ) {
632
        $analyzer = $this->getConditionAnalyzer();
633
634
        if ($analyzer->isExactSearch()) {
635
            $item = $this->find($analyzer->identifierConditionValues(), $columns);
636
637
            return $this->getModel()->newCollection([$item]);
638
        }
639
640
        $raw = $this->toDynamoDbQuery($columns, $limit);
641
642
        if ($useIterator) {
643
            $iterator = $this->client->getIterator($raw->op, $raw->query);
644
645
            if (isset($raw->query['Limit'])) {
646
                $iterator = new \LimitIterator($iterator, 0, $raw->query['Limit']);
647
            }
648
        } else {
649
            if ($raw->op === 'Scan') {
650
                $res = $this->client->scan($raw->query);
651
            } else {
652
                $res = $this->client->query($raw->query);
653
            }
654
655
            $this->lastEvaluatedKey = array_get($res, 'LastEvaluatedKey');
656
            $iterator = $res['Items'];
657
        }
658
659
        $results = [];
660
661
        foreach ($iterator as $item) {
662
            $item = DynamoDb::unmarshalItem($item);
663
            $model = $this->model->newInstance([], true);
664
            $model->setRawAttributes($item, true);
665
            $results[] = $model;
666
        }
667
668
        return $this->getModel()->newCollection($results);
669
    }
670
671
    /**
672
     * Return the raw DynamoDb query
673
     *
674
     * @param array $columns
675
     * @param int $limit
676
     * @return RawDynamoDbQuery
677
     */
678
    public function toDynamoDbQuery(
679
        $columns = [],
680
        $limit = DynamoDbQueryBuilder::MAX_LIMIT
681
    ) {
682
        $this->applyScopes();
683
684
        $this->resetExpressions();
685
686
        $op = 'Scan';
687
        $queryBuilder = DynamoDb::table($this->model->getTable());
688
689
        if (! empty($this->wheres)) {
690
            $analyzer = $this->getConditionAnalyzer();
691
692
            if ($keyConditions = $analyzer->keyConditions()) {
693
                $op = 'Query';
694
                $queryBuilder->setKeyConditionExpression($this->keyConditionExpression->parse($keyConditions));
695
            }
696
697
            if ($filterConditions = $analyzer->filterConditions()) {
698
                $queryBuilder->setFilterExpression($this->filterExpression->parse($filterConditions));
699
            }
700
701
            if ($index = $analyzer->index()) {
702
                $queryBuilder->setIndexName($index->name);
703
            }
704
        }
705
706
        if ($this->index) {
707
            // If user specifies the index manually, respect that
708
            $queryBuilder->setIndexName($this->index);
709
        }
710
711
        if ($limit !== static::MAX_LIMIT) {
712
            $queryBuilder->setLimit($limit);
713
        }
714
715
        if (!empty($columns)) {
716
            // Either we try to get the count or specific columns
717
            if ($columns == ['count(*)']) {
718
                $queryBuilder->setSelect('COUNT');
719
            } else {
720
                $queryBuilder->setProjectionExpression($this->projectionExpression->parse($columns));
721
            }
722
        }
723
724
        if (!empty($this->lastEvaluatedKey)) {
725
            $queryBuilder->setExclusiveStartKey($this->lastEvaluatedKey);
726
        }
727
728
        $queryBuilder
729
            ->setExpressionAttributeNames($this->expressionAttributeNames->all())
730
            ->setExpressionAttributeValues($this->expressionAttributeValues->all());
731
732
        $raw = with(new RawDynamoDbQuery($op, $queryBuilder->query))->finalize();
733
734
        if ($this->decorator) {
735
            call_user_func($this->decorator, $raw);
736
        }
737
738
        return $raw;
739
    }
740
741
    /**
742
     * @return Analyzer
743
     */
744
    protected function getConditionAnalyzer()
745
    {
746
        return with(new Analyzer)
747
            ->on($this->model)
748
            ->withIndex($this->index)
749
            ->analyze($this->wheres);
750
    }
751
752
    /**
753
     * Return key for DynamoDb query.
754
     *
755
     * @param array|null $modelKeys
756
     * @return array
757
     *
758
     * e.g.
759
     * [
760
     *   'id' => ['S' => 'foo'],
761
     * ]
762
     *
763
     * or
764
     *
765
     * [
766
     *   'id' => ['S' => 'foo'],
767
     *   'id2' => ['S' => 'bar'],
768
     * ]
769
     */
770
    protected function getDynamoDbKey($modelKeys = null)
771
    {
772
        $modelKeys = $modelKeys ?: $this->model->getKeys();
773
774
        $keys = [];
775
776
        foreach ($modelKeys as $key => $value) {
777
            if (is_null($value)) {
778
                continue;
779
            }
780
            $keys[$key] = DynamoDb::marshalValue($value);
781
        }
782
783
        return $keys;
784
    }
785
786
    protected function isMultipleIds($id)
787
    {
788
        $keys = collect($this->model->getKeyNames());
789
790
        // could be ['id' => 'foo'], ['id1' => 'foo', 'id2' => 'bar']
791
        $single = $keys->first(function ($name) use ($id) {
792
            return !isset($id[$name]);
793
        }) === null;
794
795
        if ($single) {
796
            return false;
797
        }
798
799
        // could be ['foo', 'bar'], [['id1' => 'foo', 'id2' => 'bar'], ...]
800
        return $this->model->hasCompositeKey() ? is_array(array_first($id)) : is_array($id);
801
    }
802
803
    /**
804
     * @return DynamoDbModel
805
     */
806
    public function getModel()
807
    {
808
        return $this->model;
809
    }
810
811
    /**
812
     * @return DynamoDbClient
813
     */
814
    public function getClient()
815
    {
816
        return $this->client;
817
    }
818
819
    /**
820
     * Register a new global scope.
821
     *
822
     * @param  string  $identifier
823
     * @param  \Illuminate\Database\Eloquent\Scope|\Closure  $scope
824
     * @return $this
825
     */
826
    public function withGlobalScope($identifier, $scope)
827
    {
828
        $this->scopes[$identifier] = $scope;
829
830
        if (method_exists($scope, 'extend')) {
831
            $scope->extend($this);
832
        }
833
834
        return $this;
835
    }
836
837
    /**
838
     * Remove a registered global scope.
839
     *
840
     * @param  \Illuminate\Database\Eloquent\Scope|string  $scope
841
     * @return $this
842
     */
843
    public function withoutGlobalScope($scope)
844
    {
845
        if (! is_string($scope)) {
846
            $scope = get_class($scope);
847
        }
848
849
        unset($this->scopes[$scope]);
850
851
        $this->removedScopes[] = $scope;
852
853
        return $this;
854
    }
855
856
    /**
857
     * Remove all or passed registered global scopes.
858
     *
859
     * @param  array|null  $scopes
860
     * @return $this
861
     */
862
    public function withoutGlobalScopes(array $scopes = null)
863
    {
864
        if (is_array($scopes)) {
865
            foreach ($scopes as $scope) {
866
                $this->withoutGlobalScope($scope);
867
            }
868
        } else {
869
            $this->scopes = [];
870
        }
871
872
        return $this;
873
    }
874
875
    /**
876
     * Get an array of global scopes that were removed from the query.
877
     *
878
     * @return array
879
     */
880
    public function removedScopes()
881
    {
882
        return $this->removedScopes;
883
    }
884
885
    /**
886
     * Apply the scopes to the Eloquent builder instance and return it.
887
     *
888
     * @return DynamoDbQueryBuilder
889
     */
890
    public function applyScopes()
891
    {
892
        if (! $this->scopes) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $this->scopes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
893
            return $this;
894
        }
895
896
        $builder = $this;
897
898
        foreach ($builder->scopes as $identifier => $scope) {
899
            if (! isset($builder->scopes[$identifier])) {
900
                continue;
901
            }
902
903
            $builder->callScope(function (DynamoDbQueryBuilder $builder) use ($scope) {
904
                // If the scope is a Closure we will just go ahead and call the scope with the
905
                // builder instance. The "callScope" method will properly group the clauses
906
                // that are added to this query so "where" clauses maintain proper logic.
907
                if ($scope instanceof Closure) {
908
                    $scope($builder);
909
                }
910
911
                // If the scope is a scope object, we will call the apply method on this scope
912
                // passing in the builder and the model instance. After we run all of these
913
                // scopes we will return back the builder instance to the outside caller.
914
                if ($scope instanceof Scope) {
915
                    throw new NotSupportedException('Scope object is not yet supported');
916
                }
917
            });
918
919
            $builder->withoutGlobalScope($identifier);
920
        }
921
922
        return $builder;
923
    }
924
925
    /**
926
     * Apply the given scope on the current builder instance.
927
     *
928
     * @param  callable  $scope
929
     * @param  array  $parameters
930
     * @return mixed
931
     */
932
    protected function callScope(callable $scope, $parameters = [])
933
    {
934
        array_unshift($parameters, $this);
935
936
        // $query = $this->getQuery();
937
938
        // // We will keep track of how many wheres are on the query before running the
939
        // // scope so that we can properly group the added scope constraints in the
940
        // // query as their own isolated nested where statement and avoid issues.
941
        // $originalWhereCount = is_null($query->wheres)
942
        //             ? 0 : count($query->wheres);
943
944
        $result = $scope(...array_values($parameters)) ?: $this;
945
946
        // if (count((array) $query->wheres) > $originalWhereCount) {
947
        //     $this->addNewWheresWithinGroup($query, $originalWhereCount);
948
        // }
949
950
        return $result;
951
    }
952
953
    /**
954
     * Dynamically handle calls into the query instance.
955
     *
956
     * @param  string  $method
957
     * @param  array  $parameters
958
     * @return mixed
959
     */
960
    public function __call($method, $parameters)
961
    {
962
        if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
963
            return $this->callScope([$this->model, $scope], $parameters);
964
        }
965
966
        return $this;
967
    }
968
}
969