Passed
Push — master ( 9dbdd9...d5a428 )
by Alexander
04:15
created

InConditionBuilder   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Test Coverage

Coverage 97.85%

Importance

Changes 0
Metric Value
eloc 97
dl 0
loc 221
ccs 91
cts 93
cp 0.9785
rs 8.4
c 0
b 0
f 0
wmc 50

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getRawValuesFromTraversableObject() 0 12 3
A buildSubqueryInCondition() 0 19 5
B buildValues() 0 28 9
A getNullCondition() 0 6 2
B buildCompositeInCondition() 0 25 8
F build() 0 69 23

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 (is_array($values)) {
70
            $rawValues = $values;
71 531
        } elseif ($values instanceof \Traversable) {
0 ignored issues
show
introduced by
$values is always a sub-type of Traversable.
Loading history...
72 481
            $rawValues = $this->getRawValuesFromTraversableObject($values);
73 50
        }
74 50
75
        $nullCondition = null;
76
        $nullConditionOperator = null;
77 531
        if (isset($rawValues) && in_array(null, $rawValues, true)) {
78 30
            $nullCondition = $this->getNullCondition($operator, $column);
79 30
            $nullConditionOperator = $operator === 'IN' ? 'OR' : 'AND';
80
        }
81
82 531
        $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

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

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