Passed
Pull Request — master (#163)
by Wilmer
12:24
created

InConditionBuilder::getNullCondition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 9
ccs 0
cts 0
cp 0
crap 6
rs 10
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 51
     * @return string the raw SQL that will not be additionally escaped or quoted.
28
     */
29 51
    public function build(ExpressionInterface $expression, array &$params = []): string
30 51
    {
31 51
        $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
        $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 51
        $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
        if ($column === []) {
36
            /* no columns to test against */
37
            return $operator === 'IN' ? '0=1' : '';
38 51
        }
39 10
40
        if ($values instanceof Query) {
41
            return $this->buildSubqueryInCondition($operator, $column, $values, $params);
42 41
        }
43
44 3
        if (!is_array($values) && !$values instanceof Traversable) {
45
            /* ensure values is an array */
46 41
            $values = (array) $values;
47 10
        }
48
49
        if (is_array($column)) {
50 31
            if (count($column) > 1) {
51
                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
                $column = reset($column);
54 31
            }
55 31
        }
56
57
        if ($column instanceof Traversable) {
58
            if (iterator_count($column) > 1) {
59 31
                return $this->buildCompositeInCondition($operator, $column, $values, $params);
60 31
            } else {
61
                $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 31
                $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 22
            }
64
        }
65
66 9
        if (is_array($values)) {
67
            $rawValues = $values;
68 9
        } elseif ($values instanceof Traversable) {
0 ignored issues
show
introduced by
$values is always a sub-type of Traversable.
Loading history...
69
            $rawValues = $this->getRawValuesFromTraversableObject($values);
70
        }
71
72
        if (isset($rawValues) && in_array(null, $rawValues, true)) {
73
            $nullCondition = $this->getNullCondition($operator, $column);
74
            $nullConditionOperator = $operator === 'IN' ? 'OR' : 'AND';
75
        }
76
77
        $sqlValues = $this->buildValues($expression, $values, $params);
78
79
        if (empty($sqlValues)) {
80 31
            if (!isset($nullCondition)) {
81
                return $operator === 'IN' ? '0=1' : '';
82 31
            }
83 31
            return $nullCondition;
84
        }
85 31
86 31
        if (strpos($column, '(') === false) {
87
            $column = $this->queryBuilder->getDb()->quoteColumnName($column);
88
        }
89 31
90
        if (count($sqlValues) > 1) {
91 31
            $sql = "$column $operator (" . implode(', ', $sqlValues) . ')';
92 3
        } else {
93
            $operator = $operator === 'IN' ? '=' : '<>';
94 31
            $sql = $column . $operator . reset($sqlValues);
95
        }
96
97
        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 31
    }
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
    protected function buildValues(ConditionInterface $condition, $values, array &$params = []): array
110
    {
111 10
        $sqlValues = [];
112
        $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 10
114
        if (is_array($column)) {
115 10
            $column = reset($column);
116 4
        }
117 4
118 4
        if ($column instanceof Traversable) {
119
            $column->rewind();
120
            $column = $column->current();
121
        }
122 4
123
        foreach ($values as $i => $value) {
124
            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 6
                $value = isset($value[$column]) ? $value[$column] : null;
126 6
            }
127
128
            if ($value === null) {
129 6
                continue;
130
            } elseif ($value instanceof ExpressionInterface) {
131
                $sqlValues[$i] = $this->queryBuilder->buildExpression($value, $params);
132
            } else {
133
                $sqlValues[$i] = $this->queryBuilder->bindParam($value, $params);
134
            }
135
        }
136
137
        return $sqlValues;
138
    }
139
140
    /**
141
     * Builds SQL for IN condition.
142 8
     *
143
     * @param string $operator
144 8
     * @param array|string $columns
145 8
     * @param Query $values
146 8
     * @param array $params
147 8
     *
148 8
     * @return string SQL
149 8
     */
150
    protected function buildSubqueryInCondition(string $operator, $columns, Query $values, array &$params = []): string
151
    {
152
        $sql = $this->queryBuilder->buildExpression($values, $params);
153
154 8
        if (is_array($columns)) {
155
            foreach ($columns as $i => $col) {
156
                if (strpos($col, '(') === false) {
157 8
                    $columns[$i] = $this->queryBuilder->getDb()->quoteColumnName($col);
158
                }
159
            }
160
161 8
            return '(' . implode(', ', $columns) . ") $operator $sql";
162 8
        }
163 8
164 8
        if (strpos($columns, '(') === false) {
165
            $columns = $this->queryBuilder->getDb()->quoteColumnName($columns);
166
        }
167 8
168
        return "$columns $operator $sql";
169
    }
170 1
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
    protected function buildCompositeInCondition(string $operator, $columns, $values, array &$params = []): string
182
    {
183
        $vss = [];
184
        foreach ($values as $value) {
185
            $vs = [];
186
187
            foreach ($columns as $column) {
188
                if (isset($value[$column])) {
189
                    $vs[] = $this->queryBuilder->bindParam($value[$column], $params);
190
                } else {
191
                    $vs[] = 'NULL';
192
                }
193
            }
194
            $vss[] = '(' . implode(', ', $vs) . ')';
195
        }
196
197
        if (empty($vss)) {
198
            return $operator === 'IN' ? '0=1' : '';
199
        }
200
201
        $sqlColumns = [];
202
        foreach ($columns as $i => $column) {
203
            $sqlColumns[] = strpos($column, '(') === false
204
                ? $this->queryBuilder->getDb()->quoteColumnName($column) : $column;
205
        }
206
207
        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
    protected function getNullCondition(string $operator, string $column): string
219
    {
220
        $column = $this->queryBuilder->getDb()->quoteColumnName($column);
221
222
        if ($operator === 'IN') {
223
            return sprintf('%s IS NULL', $column);
224
        }
225
226
        return sprintf('%s IS NOT NULL', $column);
227
    }
228
229
    /**
230
     * @param Traversable $traversableObject
231
     *
232
     * @return array raw values
233
     */
234
    protected function getRawValuesFromTraversableObject(Traversable $traversableObject): array
235
    {
236
        $rawValues = [];
237
        foreach ($traversableObject as $value) {
238
            if (is_array($value)) {
239
                $values = array_values($value);
240
                $rawValues = array_merge($rawValues, $values);
241
            } else {
242
                $rawValues[] = $value;
243
            }
244
        }
245
246
        return $rawValues;
247
    }
248
}
249