Passed
Pull Request — master (#19152)
by Alexander
53:55 queued 17:50
created

InConditionBuilder   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Test Coverage

Coverage 97.85%

Importance

Changes 0
Metric Value
eloc 109
dl 0
loc 241
ccs 91
cts 93
cp 0.9785
rs 5.5199
c 0
b 0
f 0
wmc 56

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getRawValuesFromTraversableObject() 0 12 3
B buildSubqueryInCondition() 0 25 7
B buildValues() 0 32 10
A getNullCondition() 0 6 2
F build() 0 73 24
B buildCompositeInCondition() 0 31 10

How to fix   Complexity   

Complex Class

Complex classes like InConditionBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InConditionBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db\conditions;
9
10
use yii\db\ExpressionBuilderInterface;
11
use yii\db\ExpressionBuilderTrait;
12
use yii\db\ExpressionInterface;
13
use yii\db\Query;
14
15
/**
16
 * Class InConditionBuilder builds objects of [[InCondition]]
17
 *
18
 * @author Dmytro Naumenko <[email protected]>
19
 * @since 2.0.14
20
 */
21
class InConditionBuilder implements ExpressionBuilderInterface
22
{
23
    use ExpressionBuilderTrait;
24
25
26
    /**
27
     * Method builds the raw SQL from the $expression that will not be additionally
28
     * escaped or quoted.
29
     *
30
     * @param ExpressionInterface|InCondition $expression the expression to be built.
31
     * @param array $params the binding parameters.
32
     * @return string the raw SQL that will not be additionally escaped or quoted.
33
     */
34 587
    public function build(ExpressionInterface $expression, array &$params = [])
35
    {
36 587
        $operator = strtoupper($expression->getOperator());
0 ignored issues
show
Bug introduced by
The method getOperator() does not exist on yii\db\ExpressionInterface. It seems like you code against a sub-type of said class. However, the method does not exist in yii\db\JsonExpression or yii\db\ArrayExpression or yii\db\PdoValue or yii\db\conditions\ConditionInterface or yii\db\conditions\HashCondition or yii\db\conditions\NotCondition. Are you sure you never get one of those? ( Ignorable by Annotation )

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

36
        $operator = strtoupper($expression->/** @scrutinizer ignore-call */ getOperator());
Loading history...
37 587
        $column = $expression->getColumn();
0 ignored issues
show
Bug introduced by
The method getColumn() does not exist on yii\db\ExpressionInterface. It seems like you code against a sub-type of yii\db\ExpressionInterface such as yii\db\Query or yii\db\Expression or yii\db\conditions\InCondition or yii\db\conditions\BetweenCondition or yii\db\conditions\SimpleCondition. ( Ignorable by Annotation )

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

37
        /** @scrutinizer ignore-call */ 
38
        $column = $expression->getColumn();
Loading history...
38 587
        $values = $expression->getValues();
0 ignored issues
show
Bug introduced by
The method getValues() does not exist on yii\db\ExpressionInterface. It seems like you code against a sub-type of yii\db\ExpressionInterface such as yii\db\Query or yii\db\Expression or yii\db\conditions\InCondition. ( Ignorable by Annotation )

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

38
        /** @scrutinizer ignore-call */ 
39
        $values = $expression->getValues();
Loading history...
39
40 587
        if ($column === []) {
41
            // no columns to test against
42
            return $operator === 'IN' ? '0=1' : '';
43
        }
44
45 587
        if ($values instanceof Query) {
46 26
            return $this->buildSubqueryInCondition($operator, $column, $values, $params);
47
        }
48
49 561
        if (!is_array($values) && !$values instanceof \Traversable) {
50
            // ensure values is an array
51 15
            $values = (array) $values;
52
        }
53
54 561
        if (is_array($column)) {
55 379
            if (count($column) > 1) {
56 30
                return $this->buildCompositeInCondition($operator, $column, $values, $params);
0 ignored issues
show
Bug introduced by
It seems like $values can also be of type Traversable; however, parameter $values of yii\db\conditions\InCond...dCompositeInCondition() 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

56
                return $this->buildCompositeInCondition($operator, $column, /** @scrutinizer ignore-type */ $values, $params);
Loading history...
57
            }
58 354
            $column = reset($column);
59
        }
60
61
        if ($column instanceof \Traversable) {
62 536
            if (iterator_count($column) > 1) {
63 10
                return $this->buildCompositeInCondition($operator, $column, $values, $params);
64 5
            }
65
            $column->rewind();
0 ignored issues
show
Bug introduced by
The method rewind() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as Yaf_Config_Simple or Yaf\Session or SimpleXMLElement or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or MongoGridFSCursor or yii\test\ArrayFixture or yii\web\HeaderCollection or SplFixedArray or yii\test\BaseActiveFixture or yii\base\Model or yii\web\CookieCollection or yii\web\Session. ( Ignorable by Annotation )

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

65
            $column->/** @scrutinizer ignore-call */ 
66
                     rewind();
Loading history...
66 5
            $column = $column->current();
0 ignored issues
show
Bug introduced by
The method current() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as IntlCodePointBreakIterator or Yaf_Config_Simple or Yaf\Session or SimpleXMLElement or IntlRuleBasedBreakIterator or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or MongoGridFSCursor or yii\test\ArrayFixture or yii\web\HeaderCollection or SplFixedArray or yii\test\BaseActiveFixture or yii\base\Model or yii\web\CookieCollection or IntlBreakIterator or yii\web\Session. ( Ignorable by Annotation )

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

66
            /** @scrutinizer ignore-call */ 
67
            $column = $column->current();
Loading history...
67 5
        }
68
69
        if ($column instanceof ExpressionInterface) {
70
            $column = $column->expression;
0 ignored issues
show
Bug introduced by
Accessing expression on the interface yii\db\ExpressionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
71 531
        }
72 481
73 50
        if (is_array($values)) {
74 50
            $rawValues = $values;
75
        } elseif ($values instanceof \Traversable) {
0 ignored issues
show
introduced by
$values is always a sub-type of Traversable.
Loading history...
76
            $rawValues = $this->getRawValuesFromTraversableObject($values);
77 531
        }
78 30
79 30
        $nullCondition = null;
80
        $nullConditionOperator = null;
81
        if (isset($rawValues) && in_array(null, $rawValues, true)) {
82 531
            $nullCondition = $this->getNullCondition($operator, $column);
83 531
            $nullConditionOperator = $operator === 'IN' ? 'OR' : 'AND';
84 60
        }
85 50
86
        $sqlValues = $this->buildValues($expression, $values, $params);
0 ignored issues
show
Bug introduced by
It seems like $values can also be of type Traversable; however, parameter $values of yii\db\conditions\InCond...nBuilder::buildValues() 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

86
        $sqlValues = $this->buildValues($expression, /** @scrutinizer ignore-type */ $values, $params);
Loading history...
87 10
        if (empty($sqlValues)) {
88
            if ($nullCondition === null) {
89
                return $operator === 'IN' ? '0=1' : '';
90 516
            }
91 516
            return $nullCondition;
92
        }
93 516
94 322
        if (strpos($column, '(') === false) {
95
            $column = $this->queryBuilder->db->quoteColumnName($column);
96 350
        }
97 350
        if (count($sqlValues) > 1) {
98
            $sql = "$column $operator (" . implode(', ', $sqlValues) . ')';
99
        } else {
100 516
            $operator = $operator === 'IN' ? '=' : '<>';
101
            $sql = $column . $operator . reset($sqlValues);
102
        }
103
104
        return $nullCondition !== null && $nullConditionOperator !== null
105
            ? sprintf('%s %s %s', $sql, $nullConditionOperator, $nullCondition)
106
            : $sql;
107
    }
108
109
    /**
110
     * Builds $values to be used in [[InCondition]]
111 531
     *
112
     * @param ConditionInterface|InCondition $condition
113 531
     * @param array $values
114 531
     * @param array $params the binding parameters
115
     * @return array of prepared for SQL placeholders
116 531
     */
117 354
    protected function buildValues(ConditionInterface $condition, $values, &$params)
118
    {
119
        $sqlValues = [];
120 531
        $column = $condition->getColumn();
0 ignored issues
show
Bug introduced by
The method getColumn() does not exist on yii\db\conditions\ConditionInterface. It seems like you code against a sub-type of yii\db\conditions\ConditionInterface such as yii\db\conditions\InCondition or yii\db\conditions\BetweenCondition or yii\db\conditions\SimpleCondition. ( Ignorable by Annotation )

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

120
        /** @scrutinizer ignore-call */ 
121
        $column = $condition->getColumn();
Loading history...
121 5
122 5
        if (is_array($column)) {
123
            $column = reset($column);
124
        }
125 531
126 526
        if ($column instanceof \Traversable) {
127 10
            $column->rewind();
128
            $column = $column->current();
129 526
        }
130 30
131 516
        if ($column instanceof ExpressionInterface) {
132 5
            $column = $column->expression;
0 ignored issues
show
Bug introduced by
Accessing expression on the interface yii\db\ExpressionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
133
        }
134 516
135
        foreach ($values as $i => $value) {
136
            if (is_array($value) || $value instanceof \ArrayAccess) {
137
                $value = isset($value[$column]) ? $value[$column] : null;
138 531
            }
139
            if ($value === null) {
140
                continue;
141
            } elseif ($value instanceof ExpressionInterface) {
142
                $sqlValues[$i] = $this->queryBuilder->buildExpression($value, $params);
143
            } else {
144
                $sqlValues[$i] = $this->queryBuilder->bindParam($value, $params);
145
            }
146
        }
147
148
        return $sqlValues;
149
    }
150 26
151
    /**
152 26
     * Builds SQL for IN condition.
153
     *
154 26
     * @param string $operator
155 4
     * @param array|string $columns
156 4
     * @param Query $values
157 4
     * @param array $params
158
     * @return string SQL
159
     */
160
    protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
161 4
    {
162
        $sql = $this->queryBuilder->buildExpression($values, $params);
163
164 22
        if (is_array($columns)) {
165 22
            foreach ($columns as $i => $col) {
166
                if ($col instanceof ExpressionInterface) {
167
                    $col = $col->expression;
0 ignored issues
show
Bug introduced by
Accessing expression on the interface yii\db\ExpressionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
168 22
                }
169
                if (strpos($col, '(') === false) {
170
                    $columns[$i] = $this->queryBuilder->db->quoteColumnName($col);
171
                }
172
            }
173
174
            return '(' . implode(', ', $columns) . ") $operator $sql";
175
        }
176
177
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of yii\db\ExpressionInterface.
Loading history...
178
            $columns = $columns->expression;
179
        }
180 14
        if (strpos($columns, '(') === false) {
181
            $columns = $this->queryBuilder->db->quoteColumnName($columns);
182 14
        }
183 14
184 14
        return "$columns $operator $sql";
185 14
    }
186 14
187 14
    /**
188
     * Builds SQL for IN condition.
189 14
     *
190
     * @param string $operator
191
     * @param array|\Traversable $columns
192 14
     * @param array $values
193
     * @param array $params
194
     * @return string SQL
195 14
     */
196
    protected function buildCompositeInCondition($operator, $columns, $values, &$params)
197
    {
198
        $vss = [];
199 14
        foreach ($values as $value) {
200 14
            $vs = [];
201 14
            foreach ($columns as $column) {
202
                if ($column instanceof ExpressionInterface) {
203
                    $column = $column->expression;
0 ignored issues
show
Bug introduced by
Accessing expression on the interface yii\db\ExpressionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
204 14
                }
205
                if (isset($value[$column])) {
206
                    $vs[] = $this->queryBuilder->bindParam($value[$column], $params);
207
                } else {
208
                    $vs[] = 'NULL';
209
                }
210
            }
211
            $vss[] = '(' . implode(', ', $vs) . ')';
212
        }
213
214
        if (empty($vss)) {
215 30
            return $operator === 'IN' ? '0=1' : '';
216 30
        }
217 30
218 15
        $sqlColumns = [];
219
        foreach ($columns as $i => $column) {
220 15
            if ($column instanceof ExpressionInterface) {
221
                $column = $column->expression;
222
            }
223
            $sqlColumns[] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column;
224
        }
225
226
        return '(' . implode(', ', $sqlColumns) . ") $operator (" . implode(', ', $vss) . ')';
227
    }
228 50
229
    /**
230 50
     * Builds is null/is not null condition for column based on operator
231 50
     *
232 50
     * @param string $operator
233 5
     * @param string $column
234 5
     * @return string is null or is not null condition
235
     * @since 2.0.31
236 50
     */
237
    protected function getNullCondition($operator, $column) {
238
        $column = $this->queryBuilder->db->quoteColumnName($column);
239 50
        if ($operator === 'IN') {
240
            return sprintf('%s IS NULL', $column);
241
        }
242
        return sprintf('%s IS NOT NULL', $column);
243
    }
244
245
    /**
246
     * @param \Traversable $traversableObject
247
     * @return array raw values
248
     * @since 2.0.31
249
     */
250
    protected function getRawValuesFromTraversableObject(\Traversable $traversableObject)
251
    {
252
        $rawValues = [];
253
        foreach ($traversableObject as $value) {
254
            if (is_array($value)) {
255
                $values = array_values($value);
256
                $rawValues = array_merge($rawValues, $values);
257
            } else {
258
                $rawValues[] = $value;
259
            }
260
        }
261
        return $rawValues;
262
    }
263
}
264