Completed
Pull Request — master (#20)
by Teye
14:16
created

HasFilter::whereFlags()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 20
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 3
crap 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A HasFilter::whereNotColumn() 0 8 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Level23\Druid\Concerns;
5
6
use Closure;
7
use InvalidArgumentException;
8
use Level23\Druid\Filters\InFilter;
9
use Level23\Druid\Filters\OrFilter;
10
use Level23\Druid\Filters\AndFilter;
11
use Level23\Druid\Filters\NotFilter;
12
use Level23\Druid\Interval\Interval;
13
use Level23\Druid\Filters\LikeFilter;
14
use Level23\Druid\Filters\BoundFilter;
15
use Level23\Druid\Filters\RegexFilter;
16
use Level23\Druid\Filters\SearchFilter;
17
use Level23\Druid\Dimensions\Dimension;
18
use Level23\Druid\Filters\FilterBuilder;
19
use Level23\Druid\Filters\BetweenFilter;
20
use Level23\Druid\Filters\IntervalFilter;
21
use Level23\Druid\Filters\SelectorFilter;
22
use Level23\Druid\Filters\FilterInterface;
23
use Level23\Druid\Filters\JavascriptFilter;
24
use Level23\Druid\Interval\IntervalInterface;
25
use Level23\Druid\Dimensions\DimensionBuilder;
26
use Level23\Druid\Extractions\ExtractionBuilder;
27
use Level23\Druid\Dimensions\DimensionInterface;
28
use Level23\Druid\Filters\ColumnComparisonFilter;
29
use Level23\Druid\Extractions\ExtractionInterface;
30
31
trait HasFilter
32
{
33
    /**
34
     * @var \Level23\Druid\Filters\FilterInterface|null
35
     */
36
    protected $filter;
37
38
    /**
39
     * Filter our results where the given dimension matches the value based on the operator.
40
     * The operator can be '=', '>', '>=', '<', '<=', '<>', '!=', 'like', 'not like', 'regex', 'not regex',
41
     * 'javascript', 'not javascript', 'search' and 'not search'
42
     *
43
     * @param string|FilterInterface|\Closure $filterOrDimensionOrClosure The dimension which you want to filter.
44
     * @param string|null                     $operator                   The operator which you want to use to filter.
45
     *                                                                    See below for a complete list of supported
46
     *                                                                    operators.
47
     * @param mixed                           $value                      The value which you want to use in your
48
     *                                                                    filter comparison
49
     * @param \Closure|null                   $extraction                 A closure which builds one or more extraction
50
     *                                                                    function. These are applied before the filter
51
     *                                                                    will be applied. So the filter will use the
52
     *                                                                    value returned by the extraction function(s).
53
     * @param string                          $boolean                    This influences how this filter will be
54
     *                                                                    joined with previous added filters. Should
55
     *                                                                    both filters apply ("and") or one or the
56
     *                                                                    other ("or") ? Default is "and".
57
     *
58
     * @return $this
59
     */
60 54
    public function where(
61
        $filterOrDimensionOrClosure,
62
        $operator = null,
63
        $value = null,
64
        Closure $extraction = null,
65
        string $boolean = 'and'
66
    ) {
67 54
        $filter = null;
68 54
        if (is_string($filterOrDimensionOrClosure)) {
69 34
            if ($value === null && $operator !== null) {
70 2
                $value    = $operator;
71 2
                $operator = '=';
72
            }
73
74 34
            if ($operator === null || $value === null) {
75 2
                throw new InvalidArgumentException('You have to supply an operator and an compare value when you supply a dimension as string');
76
            }
77
78 32
            $operator = strtolower($operator);
79
80 32
            if ($operator == '=') {
81 12
                $filter = new SelectorFilter(
82 12
                    $filterOrDimensionOrClosure,
83 12
                    (string)$value,
84 12
                    $this->getExtraction($extraction)
85
                );
86 20
            } elseif ($operator == '<>' || $operator == '!=') {
87 5
                $filter = new NotFilter(
88 5
                    new SelectorFilter($filterOrDimensionOrClosure, (string)$value, $this->getExtraction($extraction))
89
                );
90 15
            } elseif (in_array($operator, ['>', '>=', '<', '<='])) {
91 4
                $filter = new BoundFilter(
92 4
                    $filterOrDimensionOrClosure,
93 4
                    $operator,
94 4
                    (string)$value,
95 4
                    null,
96 4
                    $this->getExtraction($extraction)
97
                );
98 11
            } elseif ($operator == 'like') {
99 1
                $filter = new LikeFilter(
100 1
                    $filterOrDimensionOrClosure, $value, '\\', $this->getExtraction($extraction)
101
                );
102 10
            } elseif ($operator == 'not like') {
103 1
                $filter = new NotFilter(
104 1
                    new LikeFilter($filterOrDimensionOrClosure, $value, '\\', $this->getExtraction($extraction))
105
                );
106 9
            } elseif ($operator == 'javascript') {
107 1
                $filter = new JavascriptFilter($filterOrDimensionOrClosure, $value, $this->getExtraction($extraction));
108 8
            } elseif ($operator == 'not javascript') {
109 1
                $filter = new NotFilter(
110 1
                    new JavascriptFilter($filterOrDimensionOrClosure, $value, $this->getExtraction($extraction))
111
                );
112 7
            } elseif ($operator == 'regex' || $operator == 'regexp') {
113 2
                $filter = new RegexFilter($filterOrDimensionOrClosure, $value, $this->getExtraction($extraction));
114 5
            } elseif ($operator == 'not regex' || $operator == 'not regexp') {
115 2
                $filter = new NotFilter(
116 2
                    new RegexFilter($filterOrDimensionOrClosure, $value, $this->getExtraction($extraction))
117
                );
118 3
            } elseif ($operator == 'search') {
119 1
                $filter = new SearchFilter(
120 1
                    $filterOrDimensionOrClosure, $value, false, $this->getExtraction($extraction)
121
                );
122 2
            } elseif ($operator == 'not search') {
123 1
                $filter = new NotFilter(new SearchFilter(
124 1
                    $filterOrDimensionOrClosure, $value, false, $this->getExtraction($extraction)
125
                ));
126
            } else {
127 32
                $filter = null;
128
            }
129 21
        } elseif ($filterOrDimensionOrClosure instanceof FilterInterface) {
130 16
            $filter = $filterOrDimensionOrClosure;
131 6
        } elseif ($filterOrDimensionOrClosure instanceof Closure) {
132
133
            // lets create a bew builder object where the user can mess around with
134 2
            $builder = new FilterBuilder();
135
136
            // call the user function
137 2
            call_user_func($filterOrDimensionOrClosure, $builder);
138
139
            // Now retrieve the filter which was created and add it to our current filter set.
140 2
            $filter = $builder->getFilter();
141
        }
142
143 52
        if ($filter === null) {
144 5
            throw new InvalidArgumentException('The arguments which you have supplied cannot be parsed.');
145
        }
146
147 47
        strtolower($boolean) == 'and' ? $this->addAndFilter($filter) : $this->addOrFilter($filter);
148
149 47
        return $this;
150
    }
151
152
    /**
153
     * Build a where selection which is inverted
154
     *
155
     * @param \Closure $filterBuilder A closure which will receive a FilterBuilder instance.
156
     * @param string   $boolean       This influences how this filter will be joined with previous added filters.
157
     *                                Should both filters apply ("and") or one or the other ("or") ? Default is "and".
158
     *
159
     * @return $this
160
     */
161 5
    public function whereNot(Closure $filterBuilder, $boolean = 'and')
162
    {
163
        // lets create a bew builder object where the user can mess around with
164 5
        $builder = new FilterBuilder();
165
166
        // call the user function
167 5
        call_user_func($filterBuilder, $builder);
168
169
        // Now retrieve the filter which was created and add it to our current filter set.
170 5
        $filter = $builder->getFilter();
171 5
        if ($filter) {
172 5
            return $this->where(new NotFilter($filter), null, null, null, $boolean);
173
        }
174
175
        // Whe no filter was given, just return.
176
        return $this;
177
    }
178
179
    /**
180
     * Build a where selection which is inverted
181
     *
182
     * @param \Closure $filterBuilder A closure which will receive a FilterBuilder instance.
183
     *
184
     * @return $this
185
     */
186 1
    public function orWhereNot(Closure $filterBuilder)
187
    {
188 1
        return $this->whereNot($filterBuilder, 'or');
189
    }
190
191
    /**
192
     * This applies a filter, only it will join previous added filters with an "or" instead of an "and".
193
     * See the documentation of the "where" method for more information
194
     *
195
     * @param string|FilterInterface $filterOrDimension
196
     * @param string|null            $operator
197
     * @param mixed|null             $value
198
     * @param \Closure|null          $extraction
199
     *
200
     * @return $this
201
     * @see \Level23\Druid\Concerns\HasFilter::where()
202
     */
203 2
    public function orWhere($filterOrDimension, $operator = null, $value = null, Closure $extraction = null)
204
    {
205 2
        return $this->where($filterOrDimension, $operator, $value, $extraction, 'or');
206
    }
207
208
    /**
209
     * Filter records where the given dimension exists in the given list of items
210
     *
211
     * @param string        $dimension  The dimension which you want to filte
212
     * @param array         $items      A list of values. We will return records where the dimension is in this list.
213
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
214
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
215
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
216
     *                                  "and".
217
     *
218
     * @return $this
219
     */
220 4
    public function whereIn(string $dimension, array $items, Closure $extraction = null, $boolean = 'and')
221
    {
222 4
        $filter = new InFilter($dimension, $items, $this->getExtraction($extraction));
223
224 4
        return $this->where($filter, null, null, null, $boolean);
225
    }
226
227
    /**
228
     * Filter records where the given dimension exists in the given list of items.
229
     *
230
     * If there are previously defined filters, this filter will be joined with an "or".
231
     *
232
     * @param string        $dimension  The dimension which you want to filte
233
     * @param array         $items      A list of values. We will return records where the dimension is in this list.
234
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
235
     *
236
     * @return $this
237
     */
238 1
    public function orWhereIn(string $dimension, array $items, Closure $extraction = null)
239
    {
240 1
        return $this->whereIn($dimension, $items, $extraction, 'or');
241
    }
242
243
    /**
244
     * Filter records where dimensionA is equal to dimensionB.
245
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
246
     * you to select a dimension and apply extraction functions if needed.
247
     *
248
     * Example:
249
     * ```php
250
     * $builder->whereColumn('initials', function(DimensionBuilder $dimensionBuilder) {
251
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
252
     *     $extractionBuilder->substring(0, 1);
253
     *   });
254
     * });
255
     * ```
256
     *
257
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
258
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
259
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
260
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
261
     * @param string         $boolean    This influences how this filter will be joined with previous added filters.
262
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
263
     *                                   "and".
264
     *
265
     * @return $this
266
     */
267 2
    public function whereColumn($dimensionA, $dimensionB, string $boolean = 'and')
268
    {
269 2
        $filter = new ColumnComparisonFilter(
270 2
            $this->columnCompareDimension($dimensionA),
271 2
            $this->columnCompareDimension($dimensionB)
272
        );
273
274 2
        return $this->where($filter, null, null, null, $boolean);
275
    }
276
277
    /**
278
     * Filter records where dimensionA is equal to dimensionB.
279
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
280
     * you to select a dimension and apply extraction functions if needed.
281
     *
282
     * Example:
283
     * ```php
284
     * $builder->orWhereColumn('initials', function(DimensionBuilder $dimensionBuilder) {
285
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
286
     *     $extractionBuilder->substring(0, 1);
287
     *   });
288
     * });
289
     * ```
290
     *
291
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
292
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
293
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
294
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
295
     *
296
     * @return $this
297
     */
298 1
    public function orWhereColumn($dimensionA, $dimensionB)
299
    {
300 1
        return $this->whereColumn($dimensionA, $dimensionB, 'or');
301
    }
302
303
    /**
304
     * Filter records where dimensionA is NOT equal to dimensionB.
305
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
306
     * you to select a dimension and apply extraction functions if needed.
307
     *
308
     * Example:
309
     * ```php
310
     * $builder->whereNotColumn('initials', function(DimensionBuilder $dimensionBuilder) {
311
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
312
     *     $extractionBuilder->substring(0, 1);
313
     *   });
314
     * });
315
     * ```
316
     *
317
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
318
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
319
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
320
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
321
     * @param string         $boolean    This influences how this filter will be joined with previous added filters.
322
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
323
     *                                   "and".
324
     *
325
     * @return $this
326
     * @deprecated Use the whereNot() method combined with a whereColumn instead.
327
     */
328 1
    public function whereNotColumn($dimensionA, $dimensionB, string $boolean = 'and')
329
    {
330 1
        $filter = new ColumnComparisonFilter(
331 1
            $this->columnCompareDimension($dimensionA),
332 1
            $this->columnCompareDimension($dimensionB)
333
        );
334
335 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
336
    }
337
338
    /**
339
     * Filter records where dimensionA is NOT equal to dimensionB.
340
     * You can either supply a string or a Closure. The Closure will receive a DimensionBuilder object, which allows
341
     * you to select a dimension and apply extraction functions if needed.
342
     *
343
     * Example:
344
     * ```php
345
     * $builder->orWhereNotColumn('initials', function(DimensionBuilder $dimensionBuilder) {
346
     *   $dimensionBuilder->select('first_name', function(ExtractionBuilder $extractionBuilder) {
347
     *     $extractionBuilder->substring(0, 1);
348
     *   });
349
     * });
350
     * ```
351
     *
352
     * @param string|Closure $dimensionA The dimension which you want to compare, or a Closure which will receive a
353
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
354
     * @param string|Closure $dimensionB The dimension which you want to compare, or a Closure which will receive a
355
     *                                   DimensionBuilder which allows you to select a dimension in a more advance way.
356
     *
357
     * @return $this
358
     */
359 1
    public function orWhereNotColumn($dimensionA, $dimensionB)
360
    {
361
        return $this->whereNot(function (FilterBuilder $builder) use ($dimensionA, $dimensionB) {
362 1
            $builder->whereColumn($dimensionA, $dimensionB, 'or');
363 1
        });
364
    }
365
366
    /**
367
     * This filter will select records where the given dimension is greater than or equal to the given minValue, and
368
     * less than or equal to the given $maxValue.
369
     *
370
     * So in SQL syntax, this would be:
371
     * ```
372
     * WHERE dimension => $minValue AND dimension <= $maxValue
373
     * ```
374
     *
375
     * @param string        $dimension   The dimension which you want to filter
376
     * @param string|int    $minValue    The minimum value where the dimension should match. It should be equal or
377
     *                                   greater than this value.
378
     * @param string|int    $maxValue    The maximum value where the dimension should match. It should be less than
379
     *                                   this value.
380
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
381
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
382
     *                                   between filter. Can be one of the following values: "lexicographic",
383
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
384
     *                                   more details. By default it will be "numeric" if the values are
385
     *                                   numeric, otherwise it will be "lexicographic"
386
     * @param string        $boolean     This influences how this filter will be joined with previous added filters.
387
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
388
     *                                   "and".
389
     *
390
     * @return $this
391
     */
392 2
    public function whereBetween(
393
        string $dimension,
394
        $minValue,
395
        $maxValue,
396
        Closure $extraction = null,
397
        string $ordering = null,
398
        string $boolean = 'and'
399
    ) {
400 2
        $filter = new BetweenFilter($dimension, $minValue, $maxValue, $ordering, $this->getExtraction($extraction));
401
402 2
        return $this->where($filter, null, null, null, $boolean);
403
    }
404
405
    /**
406
     * This filter will select records where the given dimension is greater than or equal to the given minValue, and
407
     * less than or equal to the given $maxValue.
408
     *
409
     * This method will join previous added filters with an "or" instead of an "and".
410
     *
411
     * So in SQL syntax, this would be:
412
     * ```
413
     * WHERE (dimension => $minValue AND dimension <= $maxValue) or .... (other filters here)
414
     * ```
415
     *
416
     * @param string        $dimension   The dimension which you want to filter
417
     * @param string|int    $minValue    The minimum value where the dimension should match. It should be equal or
418
     *                                   greater than this value.
419
     * @param string|int    $maxValue    The maximum value where the dimension should match. It should be less than
420
     *                                   this value.
421
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
422
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
423
     *                                   between filter. Can be one of the following values: "lexicographic",
424
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
425
     *                                   more details. By default it will be "numeric" if the values are
426
     *                                   numeric, otherwise it will be "lexicographic"
427
     *
428
     * @return $this
429
     */
430 1
    public function orWhereBetween(
431
        string $dimension,
432
        $minValue,
433
        $maxValue,
434
        Closure $extraction = null,
435
        string $ordering = null
436
    ) {
437 1
        return $this->whereBetween($dimension, $minValue, $maxValue, $extraction, $ordering, 'or');
438
    }
439
440
    /**
441
     * This filter will select records where the given dimension is NOT between the given min and max value.
442
     *
443
     * So in SQL syntax, this would be:
444
     * ```
445
     * WHERE dimension < $minValue AND dimension > $maxValue
446
     * ```
447
     *
448
     * @param string        $dimension   The dimension which you want to filter
449
     * @param string|int    $minValue    The minimum value where the dimension should NOT match. It should be equal or
450
     *                                   greater than this value.
451
     * @param string|int    $maxValue    The maximum value where the dimension should NOT match. It should be less than
452
     *                                   this value.
453
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
454
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
455
     *                                   between filter. Can be one of the following values: "lexicographic",
456
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
457
     *                                   more details. By default it will be "numeric" if the values are
458
     *                                   numeric, otherwise it will be "lexicographic"
459
     * @param string        $boolean     This influences how this filter will be joined with previous added filters.
460
     *                                   Should both filters apply ("and") or one or the other ("or") ? Default is
461
     *                                   "and".
462
     *
463
     * @return $this
464
     * @deprecated Use the whereNot() method combined with a whereBetween instead.
465
     */
466 1
    public function whereNotBetween(
467
        string $dimension,
468
        $minValue,
469
        $maxValue,
470
        Closure $extraction = null,
471
        string $ordering = null,
472
        string $boolean = 'and'
473
    ) {
474 1
        $filter = new BetweenFilter($dimension, $minValue, $maxValue, $ordering, $this->getExtraction($extraction));
475
476 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
477
    }
478
479
    /**
480
     * This filter will select records where the given dimension is NOT between the given min and max value.
481
     *
482
     * So in SQL syntax, this would be:
483
     * ```
484
     * WHERE (dimension < $minValue AND dimension > $maxValue) or  .... (other filters here)
485
     * ```
486
     *
487
     * @param string        $dimension   The dimension which you want to filter
488
     * @param string|int    $minValue    The minimum value where the dimension should NOT match. It should be equal or
489
     *                                   greater than this value.
490
     * @param string|int    $maxValue    The maximum value where the dimension should NOT match. It should be less than
491
     *                                   this value.
492
     * @param \Closure|null $extraction  Extraction function to extract a different value from the dimension.
493
     * @param null|string   $ordering    Specifies the sorting order to use when comparing values against the
494
     *                                   between filter. Can be one of the following values: "lexicographic",
495
     *                                   "alphanumeric", "numeric", "strlen", "version". See Sorting Orders for
496
     *                                   more details. By default it will be "numeric" if the values are
497
     *                                   numeric, otherwise it will be "lexicographic"
498
     *
499
     * @return $this
500
     * @deprecated Use the whereNot() method combined with a whereBetween instead.
501
     */
502 1
    public function orWhereNotBetween(
503
        string $dimension,
504
        $minValue,
505
        $maxValue,
506
        Closure $extraction = null,
507
        string $ordering = null
508
    ) {
509
        return $this->whereNot(function (FilterBuilder $builder) use (
510 1
            $ordering,
511 1
            $extraction,
512 1
            $maxValue,
513 1
            $minValue,
514 1
            $dimension
515
        ) {
516 1
            $builder->whereBetween($dimension, $minValue, $maxValue, $extraction, $ordering, 'or');
517 1
        });
518
    }
519
520
    /**
521
     * Filter records where the given dimension NOT exists in the given list of items
522
     *
523
     * @param string        $dimension  The dimension which you want to filter
524
     * @param array         $items      A list of values. We will return records where the dimension is NOT in this
525
     *                                  list.
526
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
527
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
528
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
529
     *                                  "and".
530
     *
531
     * @return $this
532
     * @deprecated Use the whereNot() method combined with a whereIn instead.
533
     */
534 1
    public function whereNotIn(string $dimension, array $items, Closure $extraction = null, string $boolean = 'and')
535
    {
536 1
        $filter = new NotFilter(new InFilter($dimension, $items, $this->getExtraction($extraction)));
537
538 1
        return $this->where($filter, null, null, null, $boolean);
539
    }
540
541
    /**
542
     * Filter records where the given dimension NOT exists in the given list of items
543
     *
544
     * @param string        $dimension  The dimension which you want to filter
545
     * @param array         $items      A list of values. We will return records where the dimension is NOT in this
546
     *                                  list.
547
     * @param \Closure|null $extraction An extraction function to extract a different value from the dimension.
548
     *
549
     * @return $this
550
     * @deprecated Use the orWhereNot() method combined with a whereIn instead.
551
     */
552 1
    public function orWhereNotIn(string $dimension, array $items, Closure $extraction = null)
553
    {
554
        return $this->whereNot(function (FilterBuilder $filterBuilder) use ($extraction, $items, $dimension) {
555 1
            $filterBuilder->whereIn($dimension, $items, $extraction, 'or');
556 1
        });
557
    }
558
559
    /**
560
     * Filter on an dimension where the value does NOT exists in the given intervals array.
561
     *
562
     * The intervals array can contain the following:
563
     * - Only 2 elements, start and stop.
564
     * - an Interval object
565
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
566
     * - an array which each contain 2 elements, a start and stop date. These can be an DateTime object, a unix
567
     * timestamp or anything which can be parsed by DateTime::__construct
568
     *
569
     * So valid are:
570
     * ['now', 'tomorrow']
571
     * [['now', 'now + 1 hour'], ['tomorrow', 'tomorrow + 1 hour']]
572
     * ['2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z']
573
     *
574
     * @param string        $dimension  The dimension which you want to filter
575
     * @param array         $intervals  The interval which you do not want to match. See above for more info.
576
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
577
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
578
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
579
     *                                  "and".
580
     *
581
     * @return $this
582
     * @deprecated Use the whereNot() method combined with a whereInterval instead.
583
     */
584 1
    public function whereNotInterval(
585
        string $dimension,
586
        array $intervals,
587
        Closure $extraction = null,
588
        string $boolean = 'and'
589
    ) {
590 1
        $filter = new IntervalFilter(
591 1
            $dimension,
592 1
            $this->normalizeIntervals($intervals),
593 1
            $this->getExtraction($extraction)
594
        );
595
596 1
        return $this->where(new NotFilter($filter), null, null, null, $boolean);
597
    }
598
599
    /**
600
     * Filter on an dimension where the value does NOT exists in the given intervals array.
601
     *
602
     * The intervals array can contain the following:
603
     * - Only 2 elements, start and stop.
604
     * - an Interval object
605
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
606
     * - an array which each contain 2 elements, a start and stop date. These can be an DateTime object, a unix
607
     * timestamp or anything which can be parsed by DateTime::__construct
608
     *
609
     * So valid are:
610
     * ['now', 'tomorrow']
611
     * [['now', 'now + 1 hour'], ['tomorrow', 'tomorrow + 1 hour']]
612
     * ['2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z']
613
     *
614
     * @param string        $dimension  The dimension which you want to filter
615
     * @param array         $intervals  The interval which you do not want to match. See above for more info.
616
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
617
     *
618
     * @return $this
619
     * @deprecated Use the orWhereNot() method combined with a whereInterval instead.
620
     */
621 1
    public function orWhereNotInterval(string $dimension, array $intervals, Closure $extraction = null)
622
    {
623
        return $this->whereNot(function (FilterBuilder $filterBuilder) use ($extraction, $intervals, $dimension) {
624 1
            $filterBuilder->whereInterval($dimension, $intervals, $extraction, 'or');
625 1
        });
626
    }
627
628
    /**
629
     * Filter on an dimension where the value exists in the given intervals array.
630
     *
631
     * The intervals array can contain the following:
632
     * - an Interval object
633
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
634
     * - an array which contains 2 elements, a start and stop date. These can be an DateTime object, a unix timestamp
635
     *   or anything which can be parsed by DateTime::__construct
636
     *
637
     * @param string        $dimension  The dimension which you want to filter
638
     * @param array         $intervals  The interval which you want to match. See above for more info.
639
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
640
     * @param string        $boolean    This influences how this filter will be joined with previous added filters.
641
     *                                  Should both filters apply ("and") or one or the other ("or") ? Default is
642
     *                                  "and".
643
     *
644
     * @return $this
645
     */
646 2
    public function whereInterval(
647
        string $dimension,
648
        array $intervals,
649
        Closure $extraction = null,
650
        string $boolean = 'and'
651
    ) {
652 2
        $filter = new IntervalFilter(
653 2
            $dimension,
654 2
            $this->normalizeIntervals($intervals),
655 2
            $this->getExtraction($extraction)
656
        );
657
658 2
        return $this->where($filter, null, null, null, $boolean);
659
    }
660
661
    /**
662
     * Filter on an dimension where the value exists in the given intervals array.
663
     *
664
     * The intervals array can contain the following:
665
     * - an Interval object
666
     * - an raw interval string as used in druid. For example: 2019-04-15T08:00:00.000Z/2019-04-15T09:00:00.000Z
667
     * - an array which contains 2 elements, a start and stop date. These can be an DateTime object, a unix timestamp
668
     *   or anything which can be parsed by DateTime::__construct
669
     *
670
     * @param string        $dimension  The dimension which you want to filter
671
     * @param array         $intervals  The interval which you want to match. See above for more info.
672
     * @param \Closure|null $extraction Extraction function to extract a different value from the dimension.
673
     *
674
     * @return $this
675
     */
676 1
    public function orWhereInterval(string $dimension, array $intervals, Closure $extraction = null)
677
    {
678 1
        return $this->whereInterval($dimension, $intervals, $extraction, 'or');
679
    }
680
681
    /**
682
     * Normalize the given dimension to a DimensionInterface object.
683
     *
684
     * @param string|Closure $dimension
685
     *
686
     * @return \Level23\Druid\Dimensions\DimensionInterface
687
     * @throws InvalidArgumentException
688
     */
689 6
    protected function columnCompareDimension($dimension): DimensionInterface
690
    {
691 6
        if ($dimension instanceof Closure) {
692 2
            $builder = new DimensionBuilder();
693 2
            call_user_func($dimension, $builder);
694 2
            $dimensions = $builder->getDimensions();
695
696 2
            if (count($dimensions) != 1) {
697 1
                throw new InvalidArgumentException('Your dimension builder should select 1 dimension');
698
            }
699
700 1
            return $dimensions[0];
701
        }
702
703 4
        return new Dimension($dimension);
704
    }
705
706
    /**
707
     * Normalize the given intervals into Interval objects.
708
     *
709
     * @param array $intervals
710
     *
711
     * @return array
712
     */
713 12
    protected function normalizeIntervals(array $intervals): array
714
    {
715 12
        $first = reset($intervals);
716
717
        // If first is an array or already a druid interval string or object we do not wrap it in an array
718 12
        if (!is_array($first) && !$this->isDruidInterval($first)) {
719 9
            $intervals = [$intervals];
720
        }
721
722
        return array_map(function ($interval) {
723
724 12
            if ($interval instanceof IntervalInterface) {
725 1
                return $interval;
726
            }
727
728
            // If it is a string we explode it into to elements
729 11
            if (is_string($interval)) {
730 1
                $interval = explode('/', $interval);
731
            }
732
733
            // If the value is an array and is not empty and has either one or 2 values its an interval array
734 11
            if (is_array($interval) && !empty(array_filter($interval)) && count($interval) < 3) {
735
                /** @scrutinizer ignore-type */
736 8
                return new Interval(...$interval);
737
            }
738
739 3
            throw new InvalidArgumentException(
740
                'Invalid type given in the interval array. We cannot process ' .
741 3
                var_export($interval, true)
742
            );
743 12
        }, $intervals);
744
    }
745
746
    /**
747
     * Returns true if the argument provided is a druid interval string or interface
748
     *
749
     * @param string|IntervalInterface $interval
750
     *
751
     * @return bool
752
     */
753 11
    protected function isDruidInterval($interval)
754
    {
755 11
        if ($interval instanceof IntervalInterface) {
756 1
            return true;
757
        }
758
759 10
        return is_string($interval) && strpos($interval, '/') !== false;
760
    }
761
762
    /**
763
     * Helper method to add an OR filter
764
     *
765
     * @param FilterInterface $filter
766
     */
767 9
    protected function addOrFilter(FilterInterface $filter): void
768
    {
769 9
        if (!$this->filter instanceof FilterInterface) {
770 9
            $this->filter = $filter;
771
772 9
            return;
773
        }
774
775 5
        if ($this->filter instanceof OrFilter) {
776 1
            $this->filter->addFilter($filter);
777
778 1
            return;
779
        }
780
781 5
        $this->filter = new OrFilter([$this->filter, $filter]);
782 5
    }
783
784
    /**
785
     * Helper method to add an AND filter
786
     *
787
     * @param FilterInterface $filter
788
     */
789 43
    protected function addAndFilter(FilterInterface $filter): void
790
    {
791 43
        if (!$this->filter instanceof FilterInterface) {
792 43
            $this->filter = $filter;
793
794 43
            return;
795
        }
796
797 21
        if ($this->filter instanceof AndFilter) {
798 2
            $this->filter->addFilter($filter);
799
800 2
            return;
801
        }
802
803 21
        $this->filter = new AndFilter([$this->filter, $filter]);
804 21
    }
805
806
    /**
807
     * @return \Level23\Druid\Filters\FilterInterface|null
808
     */
809 34
    public function getFilter(): ?FilterInterface
810
    {
811 34
        return $this->filter;
812
    }
813
814
    /**
815
     * @param \Closure|null $extraction
816
     *
817
     * @return \Level23\Druid\Extractions\ExtractionInterface|null
818
     */
819 42
    private function getExtraction(?Closure $extraction): ?ExtractionInterface
820
    {
821 42
        if (empty($extraction)) {
822 41
            return null;
823
        }
824
825 1
        $builder = new ExtractionBuilder();
826 1
        call_user_func($extraction, $builder);
827
828 1
        return $builder->getExtraction();
829
    }
830
}