Passed
Push — master ( 9f7d35...3f1c7e )
by Wilmer
08:50 queued 06:32
created

InConditionBuilder::buildSubqueryInCondition()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 5
nop 4
dl 0
loc 19
ccs 10
cts 10
cp 1
crap 5
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Query\Conditions;
6
7
use Traversable;
8
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
9
use Yiisoft\Db\Expression\ExpressionBuilderTrait;
10
use Yiisoft\Db\Expression\ExpressionInterface;
11
use Yiisoft\Db\Query\Query;
12
13
/**
14
 * Class InConditionBuilder builds objects of {@see InCondition}.
15
 */
16
class InConditionBuilder implements ExpressionBuilderInterface
17
{
18
    use ExpressionBuilderTrait;
19
20
    /**
21
     * Method builds the raw SQL from the $expression that will not be additionally escaped or quoted.
22
     *
23
     * @param ExpressionInterface|InCondition $expression the expression to be built.
24
     *
25
     * @param array $params the binding parameters.
26
     *
27
     * @return string the raw SQL that will not be additionally escaped or quoted.
28
     */
29 125
    public function build(ExpressionInterface $expression, array &$params = []): string
30
    {
31 125
        $operator = strtoupper($expression->getOperator());
0 ignored issues
show
Bug introduced by
The method getOperator() does not exist on Yiisoft\Db\Expression\ExpressionInterface. It seems like you code against a sub-type of Yiisoft\Db\Expression\ExpressionInterface such as Yiisoft\Db\Query\Conditions\ConjunctionCondition or Yiisoft\Db\Query\Conditions\BetweenCondition or Yiisoft\Db\Query\Conditions\SimpleCondition or Yiisoft\Db\Query\Conditions\ExistsCondition or Yiisoft\Db\Query\Conditions\InCondition or Yiisoft\Db\Query\Conditi...BetweenColumnsCondition. ( Ignorable by Annotation )

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

31
        $operator = strtoupper($expression->/** @scrutinizer ignore-call */ getOperator());
Loading history...
32 125
        $column = $expression->getColumn();
0 ignored issues
show
Bug introduced by
The method getColumn() does not exist on Yiisoft\Db\Expression\ExpressionInterface. It seems like you code against a sub-type of Yiisoft\Db\Expression\ExpressionInterface such as Yiisoft\Db\Query\Conditions\BetweenCondition or Yiisoft\Db\Query\Conditions\SimpleCondition or Yiisoft\Db\Query\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

32
        /** @scrutinizer ignore-call */ 
33
        $column = $expression->getColumn();
Loading history...
33 125
        $values = $expression->getValues();
0 ignored issues
show
Bug introduced by
The method getValues() does not exist on Yiisoft\Db\Expression\ExpressionInterface. It seems like you code against a sub-type of Yiisoft\Db\Expression\ExpressionInterface such as Yiisoft\Db\Query\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

33
        /** @scrutinizer ignore-call */ 
34
        $values = $expression->getValues();
Loading history...
34
35 125
        if ($column === []) {
36
            /* no columns to test against */
37
            return $operator === 'IN' ? '0=1' : '';
38
        }
39
40 125
        if ($values instanceof Query) {
41 12
            return $this->buildSubqueryInCondition($operator, $column, $values, $params);
42
        }
43
44 113
        if (!is_array($values) && !$values instanceof Traversable) {
45
            /* ensure values is an array */
46 12
            $values = (array) $values;
47
        }
48
49 113
        if (is_array($column)) {
50 16
            if (count($column) > 1) {
51 12
                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 Yiisoft\Db\Query\Conditi...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

51
                return $this->buildCompositeInCondition($operator, $column, /** @scrutinizer ignore-type */ $values, $params);
Loading history...
52
            } else {
53 4
                $column = reset($column);
54
            }
55
        }
56
57 101
        if ($column instanceof Traversable) {
58 8
            if (iterator_count($column) > 1) {
59 4
                return $this->buildCompositeInCondition($operator, $column, $values, $params);
60
            } else {
61 4
                $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 Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or MongoGridFSCursor or SimpleXMLIterator. ( Ignorable by Annotation )

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

61
                $column->/** @scrutinizer ignore-call */ 
62
                         rewind();
Loading history...
62 4
                $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 IntlRuleBasedBreakIterator or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or IntlBreakIterator or MongoGridFSCursor or SimpleXMLIterator. ( Ignorable by Annotation )

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

62
                /** @scrutinizer ignore-call */ 
63
                $column = $column->current();
Loading history...
63
            }
64
        }
65
66 97
        if (is_array($values)) {
67 57
            $rawValues = $values;
68 40
        } elseif ($values instanceof Traversable) {
0 ignored issues
show
introduced by
$values is always a sub-type of Traversable.
Loading history...
69 40
            $rawValues = $this->getRawValuesFromTraversableObject($values);
70
        }
71
72 97
        if (isset($rawValues) && in_array(null, $rawValues, true)) {
73 24
            $nullCondition = $this->getNullCondition($operator, $column);
74 24
            $nullConditionOperator = $operator === 'IN' ? 'OR' : 'AND';
75
        }
76
77 97
        $sqlValues = $this->buildValues($expression, $values, $params);
78
79 97
        if (empty($sqlValues)) {
80 8
            if (!isset($nullCondition)) {
81
                return $operator === 'IN' ? '0=1' : '';
82
            }
83 8
            return $nullCondition;
84
        }
85
86 89
        if (strpos($column, '(') === false) {
87 89
            $column = $this->queryBuilder->getDb()->quoteColumnName($column);
88
        }
89
90 89
        if (count($sqlValues) > 1) {
91 53
            $sql = "$column $operator (" . implode(', ', $sqlValues) . ')';
92
        } else {
93 36
            $operator = $operator === 'IN' ? '=' : '<>';
94 36
            $sql = $column . $operator . reset($sqlValues);
95
        }
96
97 89
        return isset($nullCondition) ? sprintf('%s %s %s', $sql, $nullConditionOperator, $nullCondition) : $sql;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $nullConditionOperator does not seem to be defined for all execution paths leading up to this point.
Loading history...
98
    }
99
100
    /**
101
     * Builds $values to be used in {@see InCondition}.
102
     *
103
     * @param ConditionInterface|InCondition $condition
104
     * @param object|array $values
105
     * @param array $params the binding parameters
106
     *
107
     * @return array of prepared for SQL placeholders
108
     */
109 97
    protected function buildValues(ConditionInterface $condition, $values, array &$params = []): array
110
    {
111 97
        $sqlValues = [];
112 97
        $column = $condition->getColumn();
0 ignored issues
show
Bug introduced by
The method getColumn() does not exist on Yiisoft\Db\Query\Conditions\ConditionInterface. It seems like you code against a sub-type of Yiisoft\Db\Query\Conditions\ConditionInterface such as Yiisoft\Db\Query\Conditions\BetweenCondition or Yiisoft\Db\Query\Conditions\SimpleCondition or Yiisoft\Db\Query\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

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