Passed
Push — master ( 730f0e...9fbdc9 )
by Alexander
19:56 queued 02:18
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\Expression;
11
use yii\db\ExpressionBuilderInterface;
12
use yii\db\ExpressionBuilderTrait;
13
use yii\db\ExpressionInterface;
14
use yii\db\Query;
15
16
/**
17
 * Class InConditionBuilder builds objects of [[InCondition]]
18
 *
19
 * @author Dmytro Naumenko <[email protected]>
20
 * @since 2.0.14
21
 */
22
class InConditionBuilder implements ExpressionBuilderInterface
23
{
24
    use ExpressionBuilderTrait;
25
26
27
    /**
28
     * Method builds the raw SQL from the $expression that will not be additionally
29
     * escaped or quoted.
30
     *
31
     * @param ExpressionInterface|InCondition $expression the expression to be built.
32
     * @param array $params the binding parameters.
33
     * @return string the raw SQL that will not be additionally escaped or quoted.
34 587
     */
35
    public function build(ExpressionInterface $expression, array &$params = [])
36 587
    {
37 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

37
        $operator = strtoupper($expression->/** @scrutinizer ignore-call */ getOperator());
Loading history...
38 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

38
        /** @scrutinizer ignore-call */ 
39
        $column = $expression->getColumn();
Loading history...
39
        $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

39
        /** @scrutinizer ignore-call */ 
40
        $values = $expression->getValues();
Loading history...
40 587
41
        if ($column === []) {
42
            // no columns to test against
43
            return $operator === 'IN' ? '0=1' : '';
44
        }
45 587
46 26
        if ($values instanceof Query) {
47
            return $this->buildSubqueryInCondition($operator, $column, $values, $params);
48
        }
49 561
50
        if (!is_array($values) && !$values instanceof \Traversable) {
51 15
            // ensure values is an array
52
            $values = (array) $values;
53
        }
54 561
55 379
        if (is_array($column)) {
56 30
            if (count($column) > 1) {
57
                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

57
                return $this->buildCompositeInCondition($operator, $column, /** @scrutinizer ignore-type */ $values, $params);
Loading history...
58 354
            }
59
            $column = reset($column);
60
        }
61
62 536
        if ($column instanceof \Traversable) {
63 10
            if (iterator_count($column) > 1) {
64 5
                return $this->buildCompositeInCondition($operator, $column, $values, $params);
65
            }
66 5
            $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

66
            $column->/** @scrutinizer ignore-call */ 
67
                     rewind();
Loading history...
67 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

67
            /** @scrutinizer ignore-call */ 
68
            $column = $column->current();
Loading history...
68
        }
69
70
        if ($column instanceof Expression) {
71 531
            $column = $column->expression;
72 481
        }
73 50
74 50
        if (is_array($values)) {
75
            $rawValues = $values;
76
        } elseif ($values instanceof \Traversable) {
0 ignored issues
show
introduced by
$values is always a sub-type of Traversable.
Loading history...
77 531
            $rawValues = $this->getRawValuesFromTraversableObject($values);
78 30
        }
79 30
80
        $nullCondition = null;
81
        $nullConditionOperator = null;
82 531
        if (isset($rawValues) && in_array(null, $rawValues, true)) {
83 531
            $nullCondition = $this->getNullCondition($operator, $column);
84 60
            $nullConditionOperator = $operator === 'IN' ? 'OR' : 'AND';
85 50
        }
86
87 10
        $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

87
        $sqlValues = $this->buildValues($expression, /** @scrutinizer ignore-type */ $values, $params);
Loading history...
88
        if (empty($sqlValues)) {
89
            if ($nullCondition === null) {
90 516
                return $operator === 'IN' ? '0=1' : '';
91 516
            }
92
            return $nullCondition;
93 516
        }
94 322
95
        if (strpos($column, '(') === false) {
96 350
            $column = $this->queryBuilder->db->quoteColumnName($column);
97 350
        }
98
        if (count($sqlValues) > 1) {
99
            $sql = "$column $operator (" . implode(', ', $sqlValues) . ')';
100 516
        } else {
101
            $operator = $operator === 'IN' ? '=' : '<>';
102
            $sql = $column . $operator . reset($sqlValues);
103
        }
104
105
        return $nullCondition !== null && $nullConditionOperator !== null
106
            ? sprintf('%s %s %s', $sql, $nullConditionOperator, $nullCondition)
107
            : $sql;
108
    }
109
110
    /**
111 531
     * Builds $values to be used in [[InCondition]]
112
     *
113 531
     * @param ConditionInterface|InCondition $condition
114 531
     * @param array $values
115
     * @param array $params the binding parameters
116 531
     * @return array of prepared for SQL placeholders
117 354
     */
118
    protected function buildValues(ConditionInterface $condition, $values, &$params)
119
    {
120 531
        $sqlValues = [];
121 5
        $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

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