ExactMatchFilter::manyFilter()   F
last analyzed

Complexity

Conditions 16
Paths 265

Size

Total Lines 85
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 52
nc 265
nop 2
dl 0
loc 85
rs 3.9208
c 0
b 0
f 0

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 SilverStripe\ORM\Filters;
4
5
use SilverStripe\ORM\DataQuery;
6
use SilverStripe\ORM\DB;
7
use InvalidArgumentException;
8
9
/**
10
 * Selects textual content with an exact match between columnname and keyword.
11
 *
12
 * @todo case sensitivity switch
13
 * @todo documentation
14
 */
15
class ExactMatchFilter extends SearchFilter
16
{
17
18
    public function getSupportedModifiers()
19
    {
20
        return ['not', 'nocase', 'case'];
21
    }
22
23
    /**
24
     * Applies an exact match (equals) on a field value.
25
     *
26
     * @param DataQuery $query
27
     * @return DataQuery
28
     */
29
    protected function applyOne(DataQuery $query)
30
    {
31
        return $this->oneFilter($query, true);
32
    }
33
34
    /**
35
     * Excludes an exact match (equals) on a field value.
36
     *
37
     * @param DataQuery $query
38
     * @return DataQuery
39
     */
40
    protected function excludeOne(DataQuery $query)
41
    {
42
        return $this->oneFilter($query, false);
43
    }
44
45
    /**
46
     * Applies a single match, either as inclusive or exclusive
47
     *
48
     * @param DataQuery $query
49
     * @param bool $inclusive True if this is inclusive, or false if exclusive
50
     * @return DataQuery
51
     */
52
    protected function oneFilter(DataQuery $query, $inclusive)
53
    {
54
        $this->model = $query->applyRelation($this->relation);
55
        $field = $this->getDbName();
56
        $value = $this->getValue();
57
58
        // Null comparison check
59
        if ($value === null) {
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
60
            $where = DB::get_conn()->nullCheckClause($field, $inclusive);
61
            return $query->where($where);
62
        }
63
64
        // Value comparison check
65
        $where = DB::get_conn()->comparisonClause(
66
            $field,
67
            null,
68
            true, // exact?
69
            !$inclusive, // negate?
70
            $this->getCaseSensitive(),
71
            true
72
        );
73
        // for != clauses include IS NULL values, since they would otherwise be excluded
74
        if (!$inclusive) {
75
            $nullClause = DB::get_conn()->nullCheckClause($field, true);
76
            $where .= " OR {$nullClause}";
77
        }
78
79
        $clause = [$where => $value];
80
        
81
        return $this->aggregate ?
82
            $this->applyAggregate($query, $clause) :
0 ignored issues
show
Bug introduced by
$clause of type array<string,array|string> is incompatible with the type string expected by parameter $having of SilverStripe\ORM\Filters...ilter::applyAggregate(). ( Ignorable by Annotation )

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

82
            $this->applyAggregate($query, /** @scrutinizer ignore-type */ $clause) :
Loading history...
83
            $query->where($clause);
84
    }
85
86
    /**
87
     * Applies an exact match (equals) on a field value against multiple
88
     * possible values.
89
     *
90
     * @param DataQuery $query
91
     * @return DataQuery
92
     */
93
    protected function applyMany(DataQuery $query)
94
    {
95
        return $this->manyFilter($query, true);
96
    }
97
98
    /**
99
     * Excludes an exact match (equals) on a field value against multiple
100
     * possible values.
101
     *
102
     * @param DataQuery $query
103
     * @return DataQuery
104
     */
105
    protected function excludeMany(DataQuery $query)
106
    {
107
        return $this->manyFilter($query, false);
108
    }
109
110
    /**
111
     * Applies matches for several values, either as inclusive or exclusive
112
     *
113
     * @param DataQuery $query
114
     * @param bool $inclusive True if this is inclusive, or false if exclusive
115
     * @return DataQuery
116
     */
117
    protected function manyFilter(DataQuery $query, $inclusive)
118
    {
119
        $this->model = $query->applyRelation($this->relation);
120
        $caseSensitive = $this->getCaseSensitive();
121
122
        // Check values for null
123
        $field = $this->getDbName();
124
        $values = $this->getValue();
125
        if (empty($values)) {
126
            throw new \InvalidArgumentException("Cannot filter {$field} against an empty set");
127
        }
128
        $hasNull = in_array(null, $values, true);
0 ignored issues
show
Bug introduced by
It seems like $values can also be of type string; however, parameter $haystack of in_array() does only seem to accept 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

128
        $hasNull = in_array(null, /** @scrutinizer ignore-type */ $values, true);
Loading history...
129
        if ($hasNull) {
130
            $values = array_filter($values, function ($value) {
0 ignored issues
show
Bug introduced by
It seems like $values can also be of type string; however, parameter $input of array_filter() does only seem to accept 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

130
            $values = array_filter(/** @scrutinizer ignore-type */ $values, function ($value) {
Loading history...
131
                return $value !== null;
132
            });
133
        }
134
135
        $connective = '';
136
        if (empty($values)) {
137
            $predicate = '';
138
        } elseif ($caseSensitive === null) {
139
            // For queries using the default collation (no explicit case) we can use the WHERE .. NOT IN .. syntax,
140
            // providing simpler SQL than many WHERE .. AND .. fragments.
141
            $column = $this->getDbName();
142
            $placeholders = DB::placeholders($values);
0 ignored issues
show
Bug introduced by
It seems like $values can also be of type string; however, parameter $input of SilverStripe\ORM\DB::placeholders() does only seem to accept array|integer, 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

142
            $placeholders = DB::placeholders(/** @scrutinizer ignore-type */ $values);
Loading history...
143
            if ($inclusive) {
144
                $predicate = "$column IN ($placeholders)";
145
            } else {
146
                $predicate = "$column NOT IN ($placeholders)";
147
            }
148
        } else {
149
            // Generate reusable comparison clause
150
            $comparisonClause = DB::get_conn()->comparisonClause(
151
                $this->getDbName(),
152
                null,
153
                true, // exact?
154
                !$inclusive, // negate?
155
                $this->getCaseSensitive(),
156
                true
157
            );
158
            $count = count($values);
0 ignored issues
show
Bug introduced by
It seems like $values can also be of type string; 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

158
            $count = count(/** @scrutinizer ignore-type */ $values);
Loading history...
159
            if ($count > 1) {
160
                $connective = $inclusive ? ' OR ' : ' AND ';
161
                $conditions = array_fill(0, $count, $comparisonClause);
162
                $predicate = implode($connective, $conditions);
163
            } else {
164
                $predicate = $comparisonClause;
165
            }
166
        }
167
168
        // Always check for null when doing exclusive checks (either AND IS NOT NULL / OR IS NULL)
169
        // or when including the null value explicitly (OR IS NULL)
170
        if ($hasNull || !$inclusive) {
171
            // If excluding values which don't include null, or including
172
            // values which include null, we should do an `OR IS NULL`.
173
            // Otherwise we are excluding values that do include null, so `AND IS NOT NULL`.
174
            // Simplified from (!$inclusive && !$hasNull) || ($inclusive && $hasNull);
175
            $isNull = !$hasNull || $inclusive;
176
            $nullCondition = DB::get_conn()->nullCheckClause($field, $isNull);
177
178
            // Determine merge strategy
179
            if (empty($predicate)) {
180
                $predicate = $nullCondition;
181
            } else {
182
                // Merge null condition with predicate
183
                if ($isNull) {
184
                    $nullCondition = " OR {$nullCondition}";
185
                } else {
186
                    $nullCondition = " AND {$nullCondition}";
187
                }
188
                // If current predicate connective doesn't match the same as the null connective
189
                // make sure to group the prior condition
190
                if ($connective && (($connective === ' OR ') !== $isNull)) {
191
                    $predicate = "({$predicate})";
192
                }
193
                $predicate .= $nullCondition;
194
            }
195
        }
196
197
        $clause = [$predicate => $values];
198
199
        return $this->aggregate ?
200
            $this->applyAggregate($query, $clause) :
0 ignored issues
show
Bug introduced by
$clause of type array is incompatible with the type string expected by parameter $having of SilverStripe\ORM\Filters...ilter::applyAggregate(). ( Ignorable by Annotation )

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

200
            $this->applyAggregate($query, /** @scrutinizer ignore-type */ $clause) :
Loading history...
201
            $query->where($clause);
202
    }
203
204
    public function isEmpty()
205
    {
206
        return $this->getValue() === array() || $this->getValue() === null || $this->getValue() === '';
207
    }
208
}
209