Test Failed
Push — dev ( b2f55b...e79643 )
by 世昌
04:34
created

WherePrepareTrait::isArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 2
rs 10
1
<?php
2
3
4
namespace suda\database\statement;
5
6
use Countable;
7
use IteratorAggregate;
8
use suda\database\Binder;
9
use suda\database\exception\SQLException;
10
11
trait WherePrepareTrait
12
{
13
14
    /**
15
     * 处理?模板
16
     *
17
     * @param string $sql
18
     * @param array $parameter
19
     * @return array
20
     */
21
    public function prepareQueryMark(string $sql, array $parameter)
22
    {
23
        $binders = [];
24
        $query = preg_replace_callback('/\?/', function ($match) use (&$binders, $parameter) {
25
            $index = count($binders);
26
            if (array_key_exists($index, $parameter)) {
27
                $name = Binder::index($index);
28
                if (is_array($parameter[$index]) || $parameter[$index] instanceof IteratorAggregate) {
29
                    [$inSQL, $inBinders] = $this->prepareInParameter($parameter[$index], $index);
30
                    $binders = array_merge($binders, $inBinders);
31
                    return $inSQL;
32
                } else {
33
                    $binder = new Binder($name, $parameter[$index]);
34
                    $binders[] = $binder;
35
                    return ':' . $binder->getName();
36
                }
37
            }
38
            return $match[0];
39
        }, $sql);
40
        return [$query, $binders];
41
    }
42
43
    /**
44
     * 处理In语句
45
     * @param IteratorAggregate|array $values
46
     * @param string $name
47
     * @return array
48
     * @throws SQLException
49
     */
50
    public function prepareInParameter($values, string $name)
51
    {
52
        if ($this->countObject($values) <= 0) {
53
            throw new SQLException('on field ' . $name . ' value can\'t be empty array');
54
        }
55
        $names = [];
56
        $binders = [];
57
        foreach ($values as $value) {
58
            $_name = Binder::index($name);
59
            $binders[] = new Binder($_name, $value);
60
            $names[] = ':' . $_name;
61
        }
62
        return [implode(',', $names), $binders];
63
    }
64
65
    /**
66
     * @param array|Countable|IteratorAggregate $value
67
     * @return int
68
     */
69
    protected function countObject($value)
70
    {
71
        if (is_array($value) || $value instanceof Countable) {
72
            return count($value);
73
        }
74
        return iterator_count($value);
75
    }
76
77
    /**
78
     * @param string $name
79
     * @param string $operator
80
     * @param mixed $value
81
     * @param string $indexName
82
     * @return Query
83
     * @throws SQLException
84
     */
85
    public function createQueryOperation(string $name, string $operator, $value, string $indexName = '')
86
    {
87
        if ($value instanceof Query) {
88
            return new Query("`{$name}` {$operator} " . $value, $value->getBinder());
89
        }
90
        if ($value instanceof Statement) {
91
            return new Query("`{$name}` {$operator} (" . $value->getQuery() . ')', $value->getBinder());
92
        }
93
        if ($value instanceof IteratorAggregate || is_array($value)) {
94
            return $this->prepareIn($name, $operator, $value);
95
        }
96
        if (strlen($indexName) === 0) {
97
            $indexName = Binder::index($name);
98
        }
99
        return new Query("`{$name}` {$operator} :{$indexName}", [new Binder($indexName, $value)]);
100
    }
101
102
    /**
103
     * @param string $name
104
     * @param string $operator
105
     * @param $value
106
     * @return Query
107
     * @throws SQLException
108
     */
109
    public function getQueryForArray(string $name, string $operator, $value)
110
    {
111
        if ($value instanceof IteratorAggregate || is_array($value)) {
112
            return $this->prepareIn($name, $operator, $value);
113
        } else {
114
            return $this->createQueryOperation($name, $operator, $value);
115
        }
116
    }
117
118
    /**
119
     * @param string $where
120
     * @param array $whereBinder
121
     * @return array
122
     * @throws SQLException
123
     */
124
    public function prepareWhereString(string $where, array $whereBinder)
125
    {
126
        $newWhereBinder = [];
127
        foreach ($whereBinder as $name => $value) {
128
            $query = $this->getQueryForString($where, $name, $value);
129
            $where = $query->getQuery();
130
            $newWhereBinder = array_merge($newWhereBinder, $query->getBinder());
131
        }
132
        return [$where, $newWhereBinder];
133
    }
134
135
    /**
136
     * 准备条件列
137
     *
138
     * @param array $where
139
     * @return array
140
     * @throws SQLException
141
     */
142
    public function prepareWhere(array $where)
143
    {
144
        $whereArray = $this->normalizeWhereArray($where);
145
        $and = [];
146
        $binders = [];
147
        foreach ($whereArray as $item) {
148
            [$name, $option, $value] = $this->fixWhereArray($item);
149
            $query = $this->getQueryForArray($name, $option, $value);
150
            $and[] = $query->getQuery();
151
            $binders = array_merge($binders, $query->getBinder());
152
        }
153
        return [implode(' AND ', $and), $binders];
154
    }
155
156
    /**
157
     * @param array $item
158
     * @return array
159
     */
160
    protected function fixWhereArray(array $item) {
161
        if (count($item) === 2) {
162
            [$name, $value] = $item;
163
            return [$name, '=', $value];
164
        }
165
        return $item;
166
    }
167
168
    /**
169
     * @param array $where
170
     * @return array
171
     */
172
    protected function normalizeWhereArray(array $where)
173
    {
174
        if ($this->isNumberArray($where)) {
175
            return $where;
176
        }
177
        $newWhere = [];
178
        foreach ($where as $name => $value) {
179
            if (is_array($value)) {
180
                $newWhere[] = [$name, $value[0], $value[1]];
181
            } else {
182
                $op = $this->isArray($value)?'in':'=';
183
                $newWhere[] = [$name, $op, $value];
184
            }
185
        }
186
        return $newWhere;
187
    }
188
189
    /**
190
     * @param $value
191
     * @return bool
192
     */
193
    protected function isArray($value) {
194
        return is_array($value) || $value instanceof IteratorAggregate;
195
    }
196
197
    /**
198
     * @param array $where
199
     * @return bool
200
     */
201
    protected function isNumberArray(array $where)
202
    {
203
        return is_numeric(key($where)) && array_keys($where) === range(0, count($where) - 1);
204
    }
205
206
    /**
207
     * @param string $where
208
     * @param string $name
209
     * @param $value
210
     * @return Query
211
     * @throws SQLException
212
     */
213
    public function getQueryForString(string $where, string $name, $value)
214
    {
215
        if ($this->isArray($value)) {
216
            [$inSQL, $binders] = $this->prepareInParameter($value, $name);
217
            $where = $this->replaceQuote($name, $inSQL, $where);
218
            return new Query($where, $binders);
219
        } elseif ($value instanceof Binder) {
220
            return new Query($where, [$value]);
221
        } else {
222
            return new Query($where, [new Binder($name, $value)]);
223
        }
224
    }
225
226
    /**
227
     * 准备In
228
     *
229
     * @param string $name
230
     * @param string $operation
231
     * @param IteratorAggregate|array|Query|Statement $values
232
     * @return Query
233
     * @throws SQLException
234
     */
235
    public function prepareIn(string $name, string $operation, $values)
236
    {
237
        if ($values instanceof Query || $values instanceof Statement) {
238
            return $this->createQueryOperation($name, 'in', $values);
239
        }
240
        [$inSQL, $binders] = $this->prepareInParameter($values, $name);
241
        $sql = '`' . $name . '` ' . strtoupper($operation) . ' (' . $inSQL . ')';
242
        return new Query($sql, $binders);
243
    }
244
245
    /**
246
     * 替换占位符
247
     *
248
     * @param string $name
249
     * @param string $replace
250
     * @param string $target
251
     * @return string
252
     */
253
    public function replaceQuote(string $name, string $replace, string $target)
254
    {
255
        $name = ltrim($name, ':');
256
        return preg_replace('/(?<!_):' . preg_quote($name) . '/', $replace, $target);
257
    }
258
}
259