Passed
Push — 5.2 ( da0c84...54339d )
by liu
02:41
created

Builder::getConnection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think\db;
14
15
use PDO;
16
use think\Exception;
17
18
abstract class Builder
19
{
20
    /**
21
     * Connection对象
22
     * @var Connection
23
     */
24
    protected $connection;
25
26
    /**
27
     * 查询表达式映射
28
     * @var array
29
     */
30
    protected $exp = ['NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME'];
31
32
    /**
33
     * 查询表达式解析
34
     * @var array
35
     */
36
    protected $parser = [
37
        'parseCompare'     => ['=', '<>', '>', '>=', '<', '<='],
38
        'parseLike'        => ['LIKE', 'NOT LIKE'],
39
        'parseBetween'     => ['NOT BETWEEN', 'BETWEEN'],
40
        'parseIn'          => ['NOT IN', 'IN'],
41
        'parseExp'         => ['EXP'],
42
        'parseNull'        => ['NOT NULL', 'NULL'],
43
        'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'],
44
        'parseTime'        => ['< TIME', '> TIME', '<= TIME', '>= TIME'],
45
        'parseExists'      => ['NOT EXISTS', 'EXISTS'],
46
        'parseColumn'      => ['COLUMN'],
47
    ];
48
49
    /**
50
     * SELECT SQL表达式
51
     * @var string
52
     */
53
    protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';
54
55
    /**
56
     * INSERT SQL表达式
57
     * @var string
58
     */
59
    protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
60
61
    /**
62
     * INSERT ALL SQL表达式
63
     * @var string
64
     */
65
    protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
66
67
    /**
68
     * UPDATE SQL表达式
69
     * @var string
70
     */
71
    protected $updateSql = 'UPDATE%EXTRA% %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
72
73
    /**
74
     * DELETE SQL表达式
75
     * @var string
76
     */
77
    protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
78
79
    /**
80
     * 架构函数
81
     * @access public
82
     * @param  Connection    $connection 数据库连接对象实例
83
     */
84
    public function __construct(Connection $connection)
85
    {
86
        $this->connection = $connection;
87
    }
88
89
    /**
90
     * 获取当前的连接对象实例
91
     * @access public
92
     * @return Connection
93
     */
94
    public function getConnection(): Connection
95
    {
96
        return $this->connection;
97
    }
98
99
    /**
100
     * 注册查询表达式解析
101
     * @access public
102
     * @param  string    $name   解析方法
103
     * @param  array     $parser 匹配表达式数据
104
     * @return $this
105
     */
106
    public function bindParser(string $name, array $parser)
107
    {
108
        $this->parser[$name] = $parser;
109
        return $this;
110
    }
111
112
    /**
113
     * 数据分析
114
     * @access protected
115
     * @param  Query     $query     查询对象
116
     * @param  array     $data      数据
117
     * @param  array     $fields    字段信息
118
     * @param  array     $bind      参数绑定
119
     * @return array
120
     */
121
    protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array
122
    {
123
        if (empty($data)) {
124
            return [];
125
        }
126
127
        $options = $query->getOptions();
128
129
        // 获取绑定信息
130
        if (empty($bind)) {
131
            $bind = $query->getFieldsBindType();
132
        }
133
134
        if (empty($fields)) {
135
            if ('*' == $options['field']) {
136
                $fields = array_keys($bind);
137
            } else {
138
                $fields = $options['field'];
139
            }
140
        }
141
142
        $result = [];
143
144
        foreach ($data as $key => $val) {
145
            $item = $this->parseKey($query, $key, true);
146
147
            if ($val instanceof Raw) {
148
                $result[$item] = $val->getValue();
149
                continue;
150
            } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) {
151
                $val = json_encode($val);
152
            } elseif (is_object($val) && method_exists($val, '__toString')) {
153
                // 对象数据写入
154
                $val = $val->__toString();
155
            }
156
157
            if (false !== strpos($key, '->')) {
158
                list($key, $name) = explode('->', $key);
159
                $item             = $this->parseKey($query, $key);
160
                $result[$item]    = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key, $val, $bind) . ')';
161
            } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) {
162
                if ($options['strict']) {
163
                    throw new Exception('fields not exists:[' . $key . ']');
164
                }
165
            } elseif (is_null($val)) {
166
                $result[$item] = 'NULL';
167
            } elseif (is_array($val) && !empty($val)) {
168
                switch (strtoupper($val[0])) {
169
                    case 'INC':
170
                        $result[$item] = $item . ' + ' . floatval($val[1]);
171
                        break;
172
                    case 'DEC':
173
                        $result[$item] = $item . ' - ' . floatval($val[1]);
174
                        break;
175
                }
176
            } elseif (is_scalar($val)) {
177
                // 过滤非标量数据
178
                $result[$item] = $this->parseDataBind($query, $key, $val, $bind);
179
            }
180
        }
181
182
        return $result;
183
    }
184
185
    /**
186
     * 数据绑定处理
187
     * @access protected
188
     * @param  Query     $query     查询对象
189
     * @param  string    $key       字段名
190
     * @param  mixed     $data      数据
191
     * @param  array     $bind      绑定数据
192
     * @return string
193
     */
194
    protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string
195
    {
196
        if ($data instanceof Raw) {
197
            return $data->getValue();
198
        }
199
200
        $name = $query->bindValue($data, $bind[$key] ?? PDO::PARAM_STR);
201
202
        return ':' . $name;
203
    }
204
205
    /**
206
     * 字段名分析
207
     * @access public
208
     * @param  Query  $query    查询对象
209
     * @param  mixed  $key      字段名
210
     * @param  bool   $strict   严格检测
211
     * @return string
212
     */
213
    public function parseKey(Query $query, $key, bool $strict = false): string
214
    {
215
        return $key;
216
    }
217
218
    /**
219
     * 查询额外参数分析
220
     * @access protected
221
     * @param  Query  $query    查询对象
222
     * @param  string $extra    额外参数
223
     * @return string
224
     */
225
    protected function parseExtra(Query $query, string $extra): string
226
    {
227
        if (preg_match('/^[\w]+$/i', $extra)) {
228
            return ' ' . strtoupper($extra);
229
        } else {
230
            return '';
231
        }
232
    }
233
234
    /**
235
     * field分析
236
     * @access protected
237
     * @param  Query     $query     查询对象
238
     * @param  mixed     $fields    字段名
239
     * @return string
240
     */
241
    protected function parseField(Query $query, $fields): string
242
    {
243
        if (is_array($fields)) {
244
            // 支持 'field1'=>'field2' 这样的字段别名定义
245
            $array = [];
246
247
            foreach ($fields as $key => $field) {
248
                if ($field instanceof Raw) {
249
                    $array[] = $field->getValue();
250
                } elseif (!is_numeric($key)) {
251
                    $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true);
252
                } else {
253
                    $array[] = $this->parseKey($query, $field);
254
                }
255
            }
256
257
            $fieldsStr = implode(',', $array);
258
        } else {
259
            $fieldsStr = '*';
260
        }
261
262
        return $fieldsStr;
263
    }
264
265
    /**
266
     * table分析
267
     * @access protected
268
     * @param  Query     $query     查询对象
269
     * @param  mixed     $tables    表名
270
     * @return string
271
     */
272
    protected function parseTable(Query $query, $tables): string
273
    {
274
        $item    = [];
275
        $options = $query->getOptions();
276
277
        foreach ((array) $tables as $key => $table) {
278
            if ($table instanceof Raw) {
279
                $item[] = $table->getValue();
280
            } elseif (!is_numeric($key)) {
281
                $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table);
282
            } elseif (isset($options['alias'][$table])) {
283
                $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]);
284
            } else {
285
                $item[] = $this->parseKey($query, $table);
286
            }
287
        }
288
289
        return implode(',', $item);
290
    }
291
292
    /**
293
     * where分析
294
     * @access protected
295
     * @param  Query     $query   查询对象
296
     * @param  mixed     $where   查询条件
297
     * @return string
298
     */
299
    protected function parseWhere(Query $query, array $where): string
300
    {
301
        $options  = $query->getOptions();
302
        $whereStr = $this->buildWhere($query, $where);
303
304
        if (!empty($options['soft_delete'])) {
305
            // 附加软删除条件
306
            list($field, $condition) = $options['soft_delete'];
307
308
            $binds    = $query->getFieldsBindType();
309
            $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : '';
310
            $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, '', $binds);
311
        }
312
313
        return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
314
    }
315
316
    /**
317
     * 生成查询条件SQL
318
     * @access public
319
     * @param  Query     $query     查询对象
320
     * @param  mixed     $where     查询条件
321
     * @return string
322
     */
323
    public function buildWhere(Query $query, array $where): string
324
    {
325
        if (empty($where)) {
326
            $where = [];
327
        }
328
329
        $whereStr = '';
330
331
        $binds = $query->getFieldsBindType();
332
333
        foreach ($where as $logic => $val) {
334
            $str = [];
335
336
            foreach ($val as $value) {
337
                if ($value instanceof Raw) {
338
                    $str[] = ' ' . $logic . ' ( ' . $value->getValue() . ' )';
339
                    continue;
340
                }
341
342
                if (is_array($value)) {
343
                    if (key($value) !== 0) {
344
                        throw new Exception('where express error:' . var_export($value, true));
345
                    }
346
                    $field = array_shift($value);
347
                } elseif (!($value instanceof \Closure)) {
348
                    throw new Exception('where express error:' . var_export($value, true));
349
                }
350
351
                if ($value instanceof \Closure) {
352
                    // 使用闭包查询
353
                    $newQuery = $query->newQuery()->setConnection($this->connection);
354
                    $value($newQuery);
355
                    $whereClause = $this->buildWhere($query, $newQuery->getOptions('where') ?: []);
356
357
                    if (!empty($whereClause)) {
358
                        $str[] = ' ' . $logic . ' ( ' . $whereClause . ' )';
359
                    }
360
                } elseif (is_array($field)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $field does not seem to be defined for all execution paths leading up to this point.
Loading history...
361
                    array_unshift($value, $field);
362
                    $str2 = [];
363
                    foreach ($value as $item) {
364
                        $str2[] = $this->parseWhereItem($query, array_shift($item), $item, $logic, $binds);
365
                    }
366
367
                    $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $str2) . ' )';
368
                } elseif ($field instanceof Raw) {
369
                    $str[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $logic, $binds);
370
                } elseif (strpos($field, '|')) {
371
                    // 不同字段使用相同查询条件(OR)
372
                    $array = explode('|', $field);
373
                    $item  = [];
374
375
                    foreach ($array as $k) {
376
                        $item[] = $this->parseWhereItem($query, $k, $value, '', $binds);
377
                    }
378
379
                    $str[] = ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )';
380
                } elseif (strpos($field, '&')) {
381
                    // 不同字段使用相同查询条件(AND)
382
                    $array = explode('&', $field);
383
                    $item  = [];
384
385
                    foreach ($array as $k) {
386
                        $item[] = $this->parseWhereItem($query, $k, $value, '', $binds);
387
                    }
388
389
                    $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )';
390
                } else {
391
                    // 对字段使用表达式查询
392
                    $field = is_string($field) ? $field : '';
393
                    $str[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $logic, $binds);
394
                }
395
            }
396
397
            $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str);
398
        }
399
400
        return $whereStr;
401
    }
402
403
    // where子单元分析
404
    protected function parseWhereItem(Query $query, $field, $val, $rule = '', array $binds = []): string
405
    {
406
        // 字段分析
407
        $key = $field ? $this->parseKey($query, $field, true) : '';
408
409
        // 查询规则和条件
410
        if (!is_array($val)) {
411
            $val = is_null($val) ? ['NULL', ''] : ['=', $val];
412
        }
413
414
        list($exp, $value) = $val;
415
416
        // 对一个字段使用多个查询条件
417
        if (is_array($exp)) {
418
            $item = array_pop($val);
419
420
            // 传入 or 或者 and
421
            if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) {
422
                $rule = $item;
423
            } else {
424
                array_push($val, $item);
425
            }
426
427
            foreach ($val as $k => $item) {
428
                $str[] = $this->parseWhereItem($query, $field, $item, $rule, $binds);
429
            }
430
431
            return '( ' . implode(' ' . $rule . ' ', $str) . ' )';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $str seems to be defined by a foreach iteration on line 427. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
432
        }
433
434
        // 检测操作符
435
        $exp = strtoupper($exp);
436
        if (isset($this->exp[$exp])) {
437
            $exp = $this->exp[$exp];
438
        }
439
440
        if (is_string($field)) {
441
            $bindType = $binds[$field] ?? PDO::PARAM_STR;
442
        } else {
443
            $bindType = PDO::PARAM_STR;
444
        }
445
446
        if ($value instanceof Expression) {
447
            return $value->parse($query, $key, $exp, $field, $bindType);
448
        }
449
450
        if ($value instanceof Raw) {
451
452
        } elseif (is_object($value) && method_exists($value, '__toString')) {
453
            // 对象数据写入
454
            $value = $value->__toString();
455
        }
456
457
        if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) {
458
            if (is_string($value) && 0 === strpos($value, ':') && $query->isBind(substr($value, 1))) {
459
            } else {
460
                $name  = $query->bindValue($value, $bindType);
461
                $value = ':' . $name;
462
            }
463
        }
464
465
        // 解析查询表达式
466
        foreach ($this->parser as $fun => $parse) {
467
            if (in_array($exp, $parse)) {
468
                $whereStr = $this->$fun($query, $key, $exp, $value, $field, $bindType, $val[2] ?? 'AND');
469
                break;
470
            }
471
        }
472
473
        if (!isset($whereStr)) {
474
            throw new Exception('where express error:' . $exp);
475
        }
476
477
        return $whereStr;
478
    }
479
480
    /**
481
     * 模糊查询
482
     * @access protected
483
     * @param  Query     $query        查询对象
484
     * @param  string    $key
485
     * @param  string    $exp
486
     * @param  mixed     $value
487
     * @param  string    $field
488
     * @param  integer   $bindType
489
     * @param  string    $logic
490
     * @return string
491
     */
492
    protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string
493
    {
494
        // 模糊匹配
495
        if (is_array($value)) {
496
            foreach ($value as $item) {
497
                $name    = $query->bindValue($item, $bindType);
498
                $array[] = $key . ' ' . $exp . ' :' . $name;
499
            }
500
501
            $whereStr = '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $array seems to be defined by a foreach iteration on line 496. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Bug introduced by
' ' . strtoupper($logic) . ' ' of type string is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

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

501
            $whereStr = '(' . implode($array, /** @scrutinizer ignore-type */ ' ' . strtoupper($logic) . ' ') . ')';
Loading history...
502
        } else {
503
            $whereStr = $key . ' ' . $exp . ' ' . $value;
504
        }
505
506
        return $whereStr;
507
    }
508
509
    /**
510
     * 表达式查询
511
     * @access protected
512
     * @param  Query        $query        查询对象
513
     * @param  string       $key
514
     * @param  string       $exp
515
     * @param  Raw          $value
516
     * @param  string       $field
517
     * @param  integer      $bindType
518
     * @return string
519
     */
520
    protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string
521
    {
522
        // 表达式查询
523
        return '( ' . $key . ' ' . $value->getValue() . ' )';
524
    }
525
526
    /**
527
     * 表达式查询
528
     * @access protected
529
     * @param  Query        $query        查询对象
530
     * @param  string       $key
531
     * @param  string       $exp
532
     * @param  array        $value
533
     * @param  string       $field
534
     * @param  integer      $bindType
535
     * @return string
536
     */
537
    protected function parseColumn(Query $query, string $key, $exp, array $value, string $field, int $bindType): string
538
    {
539
        // 字段比较查询
540
        list($op, $field) = $value;
541
542
        if (!in_array(trim($op), ['=', '<>', '>', '>=', '<', '<='])) {
543
            throw new Exception('where express error:' . var_export($value, true));
544
        }
545
546
        return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field, true) . ' )';
547
    }
548
549
    /**
550
     * Null查询
551
     * @access protected
552
     * @param  Query     $query        查询对象
553
     * @param  string    $key
554
     * @param  string    $exp
555
     * @param  mixed     $value
556
     * @param  string    $field
557
     * @param  integer   $bindType
558
     * @return string
559
     */
560
    protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string
561
    {
562
        // NULL 查询
563
        return $key . ' IS ' . $exp;
564
    }
565
566
    /**
567
     * 范围查询
568
     * @access protected
569
     * @param  Query     $query        查询对象
570
     * @param  string    $key
571
     * @param  string    $exp
572
     * @param  mixed     $value
573
     * @param  string    $field
574
     * @param  integer   $bindType
575
     * @return string
576
     */
577
    protected function parseBetween(Query $query, string $key, string $exp, $value, $field, int $bindType): string
578
    {
579
        // BETWEEN 查询
580
        $data = is_array($value) ? $value : explode(',', $value);
581
582
        $min = $query->bindValue($data[0], $bindType);
583
        $max = $query->bindValue($data[1], $bindType);
584
585
        return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' ';
586
    }
587
588
    /**
589
     * Exists查询
590
     * @access protected
591
     * @param  Query     $query        查询对象
592
     * @param  string    $key
593
     * @param  string    $exp
594
     * @param  mixed     $value
595
     * @param  string    $field
596
     * @param  integer   $bindType
597
     * @return string
598
     */
599
    protected function parseExists(Query $query, string $key, string $exp, $value, string $field, int $bindType): string
600
    {
601
        // EXISTS 查询
602
        if ($value instanceof \Closure) {
603
            $value = $this->parseClosure($query, $value, false);
604
        } elseif ($value instanceof Raw) {
605
            $value = $value->getValue();
606
        } else {
607
            throw new Exception('where express error:' . $value);
608
        }
609
610
        return $exp . ' (' . $value . ')';
611
    }
612
613
    /**
614
     * 时间比较查询
615
     * @access protected
616
     * @param  Query     $query        查询对象
617
     * @param  string    $key
618
     * @param  string    $exp
619
     * @param  mixed     $value
620
     * @param  string    $field
621
     * @param  integer   $bindType
622
     * @return string
623
     */
624
    protected function parseTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
625
    {
626
        return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType);
627
    }
628
629
    /**
630
     * 大小比较查询
631
     * @access protected
632
     * @param  Query     $query        查询对象
633
     * @param  string    $key
634
     * @param  string    $exp
635
     * @param  mixed     $value
636
     * @param  string    $field
637
     * @param  integer   $bindType
638
     * @return string
639
     */
640
    protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType): string
641
    {
642
        if (is_array($value)) {
643
            throw new Exception('where express error:' . $exp . var_export($value, true));
644
        }
645
646
        // 比较运算
647
        if ($value instanceof \Closure) {
648
            $value = $this->parseClosure($query, $value);
649
        }
650
651
        return $key . ' ' . $exp . ' ' . $value;
652
    }
653
654
    /**
655
     * 时间范围查询
656
     * @access protected
657
     * @param  Query     $query        查询对象
658
     * @param  string    $key
659
     * @param  string    $exp
660
     * @param  mixed     $value
661
     * @param  string    $field
662
     * @param  integer   $bindType
663
     * @return string
664
     */
665
    protected function parseBetweenTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
666
    {
667
        if (is_string($value)) {
668
            $value = explode(',', $value);
669
        }
670
671
        return $key . ' ' . substr($exp, 0, -4)
672
        . $this->parseDateTime($query, $value[0], $field, $bindType)
673
        . ' AND '
674
        . $this->parseDateTime($query, $value[1], $field, $bindType);
675
676
    }
677
678
    /**
679
     * IN查询
680
     * @access protected
681
     * @param  Query     $query        查询对象
682
     * @param  string    $key
683
     * @param  string    $exp
684
     * @param  mixed     $value
685
     * @param  string    $field
686
     * @param  integer   $bindType
687
     * @return string
688
     */
689
    protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string
690
    {
691
        // IN 查询
692
        if ($value instanceof \Closure) {
693
            $value = $this->parseClosure($query, $value, false);
694
        } elseif ($value instanceof Raw) {
695
            $value = $value->getValue();
696
        } else {
697
            $value = array_unique(is_array($value) ? $value : explode(',', $value));
698
            $array = [];
699
700
            foreach ($value as $v) {
701
                $name    = $query->bindValue($v, $bindType);
702
                $array[] = ':' . $name;
703
            }
704
705
            $zone  = implode(',', $array);
706
            $value = empty($zone) ? "''" : $zone;
707
        }
708
709
        return $key . ' ' . $exp . ' (' . $value . ')';
710
    }
711
712
    /**
713
     * 闭包子查询
714
     * @access protected
715
     * @param  Query     $query        查询对象
716
     * @param  \Closure  $call
717
     * @param  bool      $show
718
     * @return string
719
     */
720
    protected function parseClosure(Query $query, \Closure $call, bool $show = true): string
721
    {
722
        $newQuery = $query->newQuery()->setConnection($this->connection);
723
        $call($newQuery);
724
725
        return $newQuery->buildSql($show);
726
    }
727
728
    /**
729
     * 日期时间条件解析
730
     * @access protected
731
     * @param  Query     $query        查询对象
732
     * @param  mixed     $value
733
     * @param  string    $key
734
     * @param  integer   $bindType
735
     * @return string
736
     */
737
    protected function parseDateTime(Query $query, $value, string $key, int $bindType): string
738
    {
739
        $options = $query->getOptions();
740
741
        // 获取时间字段类型
742
        if (strpos($key, '.')) {
743
            list($table, $key) = explode('.', $key);
744
745
            if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) {
746
                $table = $pos;
747
            }
748
        } else {
749
            $table = $options['table'];
750
        }
751
752
        $type = $this->connection->getTableInfo($table, 'type');
753
754
        if (isset($type[$key])) {
755
            $info = $type[$key];
756
            if (is_string($value)) {
757
                $value = strtotime($value) ?: $value;
758
            }
759
760
            if (is_int($value)) {
761
                if (preg_match('/(datetime|timestamp)/is', $info)) {
762
                    // 日期及时间戳类型
763
                    $value = date('Y-m-d H:i:s', $value);
764
                } elseif (preg_match('/(date)/is', $info)) {
765
                    // 日期及时间戳类型
766
                    $value = date('Y-m-d', $value);
767
                }
768
            }
769
        }
770
771
        $name = $query->bindValue($value, $bindType);
772
773
        return ':' . $name;
774
    }
775
776
    /**
777
     * limit分析
778
     * @access protected
779
     * @param  Query     $query        查询对象
780
     * @param  mixed     $limit
781
     * @return string
782
     */
783
    protected function parseLimit(Query $query, string $limit): string
784
    {
785
        return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : '';
786
    }
787
788
    /**
789
     * join分析
790
     * @access protected
791
     * @param  Query     $query        查询对象
792
     * @param  array     $join
793
     * @return string
794
     */
795
    protected function parseJoin(Query $query, array $join): string
796
    {
797
        $joinStr = '';
798
799
        foreach ($join as $item) {
800
            list($table, $type, $on) = $item;
801
802
            if (strpos($on, '=')) {
803
                list($val1, $val2) = explode('=', $on, 2);
804
805
                $condition = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2);
806
            } else {
807
                $condition = $on;
808
            }
809
810
            $table = $this->parseTable($query, $table);
811
812
            $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . $condition;
813
        }
814
815
        return $joinStr;
816
    }
817
818
    /**
819
     * order分析
820
     * @access protected
821
     * @param  Query     $query        查询对象
822
     * @param  array     $order
823
     * @return string
824
     */
825
    protected function parseOrder(Query $query, array $order): string
826
    {
827
        foreach ($order as $key => $val) {
828
            if ($val instanceof Raw) {
829
                $array[] = $val->getValue();
830
            } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) {
831
                $array[] = $this->parseOrderField($query, $key, $val);
832
            } elseif ('[rand]' == $val) {
833
                $array[] = $this->parseRand($query);
834
            } elseif (is_string($val)) {
835
                if (is_numeric($key)) {
836
                    list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
837
                } else {
838
                    $sort = $val;
839
                }
840
841
                if (preg_match('/^[\w\.]+$/', $key)) {
842
                    $sort    = strtoupper($sort);
843
                    $sort    = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
844
                    $array[] = $this->parseKey($query, $key, true) . $sort;
845
                } else {
846
                    throw new Exception('order express error:' . $key);
847
                }
848
            }
849
        }
850
851
        return empty($array) ? '' : ' ORDER BY ' . implode(',', $array);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $array seems to be defined by a foreach iteration on line 827. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
852
    }
853
854
    /**
855
     * 随机排序
856
     * @access protected
857
     * @param  Query     $query        查询对象
858
     * @return string
859
     */
860
    protected function parseRand(Query $query): string
861
    {
862
        return '';
863
    }
864
865
    /**
866
     * orderField分析
867
     * @access protected
868
     * @param  Query     $query        查询对象
869
     * @param  string    $key
870
     * @param  array     $val
871
     * @return string
872
     */
873
    protected function parseOrderField(Query $query, string $key, array $val): string
874
    {
875
        if (isset($val['sort'])) {
876
            $sort = $val['sort'];
877
            unset($val['sort']);
878
        } else {
879
            $sort = '';
880
        }
881
882
        $sort = strtoupper($sort);
883
        $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
884
        $bind = $query->getFieldsBindType();
885
886
        foreach ($val as $item) {
887
            $val[] = $this->parseDataBind($query, $key, $item, $bind);
888
        }
889
890
        return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort;
891
    }
892
893
    /**
894
     * group分析
895
     * @access protected
896
     * @param  Query     $query        查询对象
897
     * @param  mixed     $group
898
     * @return string
899
     */
900
    protected function parseGroup(Query $query, $group): string
901
    {
902
        if (empty($group)) {
903
            return '';
904
        }
905
906
        if (is_string($group)) {
907
            $group = explode(',', $group);
908
        }
909
910
        foreach ($group as $key) {
911
            $val[] = $this->parseKey($query, $key);
912
        }
913
914
        return ' GROUP BY ' . implode(',', $val);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $val seems to be defined by a foreach iteration on line 910. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
915
    }
916
917
    /**
918
     * having分析
919
     * @access protected
920
     * @param  Query  $query        查询对象
921
     * @param  string $having
922
     * @return string
923
     */
924
    protected function parseHaving(Query $query, string $having): string
925
    {
926
        return !empty($having) ? ' HAVING ' . $having : '';
927
    }
928
929
    /**
930
     * comment分析
931
     * @access protected
932
     * @param  Query  $query        查询对象
933
     * @param  string $comment
934
     * @return string
935
     */
936
    protected function parseComment(Query $query, string $comment): string
937
    {
938
        if (false !== strpos($comment, '*/')) {
939
            $comment = strstr($comment, '*/', true);
940
        }
941
942
        return !empty($comment) ? ' /* ' . $comment . ' */' : '';
943
    }
944
945
    /**
946
     * distinct分析
947
     * @access protected
948
     * @param  Query     $query        查询对象
949
     * @param  mixed     $distinct
950
     * @return string
951
     */
952
    protected function parseDistinct(Query $query, bool $distinct): string
953
    {
954
        return !empty($distinct) ? ' DISTINCT ' : '';
955
    }
956
957
    /**
958
     * union分析
959
     * @access protected
960
     * @param  Query     $query        查询对象
961
     * @param  array     $union
962
     * @return string
963
     */
964
    protected function parseUnion(Query $query, array $union): string
965
    {
966
        if (empty($union)) {
967
            return '';
968
        }
969
970
        $type = $union['type'];
971
        unset($union['type']);
972
973
        foreach ($union as $u) {
974
            if ($u instanceof \Closure) {
975
                $sql[] = $type . ' ' . $this->parseClosure($query, $u);
976
            } elseif (is_string($u)) {
977
                $sql[] = $type . ' ( ' . $u . ' )';
978
            }
979
        }
980
981
        return ' ' . implode(' ', $sql);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sql seems to be defined by a foreach iteration on line 973. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
982
    }
983
984
    /**
985
     * index分析,可在操作链中指定需要强制使用的索引
986
     * @access protected
987
     * @param  Query     $query        查询对象
988
     * @param  mixed     $index
989
     * @return string
990
     */
991
    protected function parseForce(Query $query, $index): string
992
    {
993
        if (empty($index)) {
994
            return '';
995
        }
996
997
        if (is_array($index)) {
998
            $index = join(',', $index);
999
        }
1000
1001
        return sprintf(" FORCE INDEX ( %s ) ", $index);
1002
    }
1003
1004
    /**
1005
     * 设置锁机制
1006
     * @access protected
1007
     * @param  Query         $query        查询对象
1008
     * @param  bool|string   $lock
1009
     * @return string
1010
     */
1011
    protected function parseLock(Query $query, $lock = false): string
1012
    {
1013
        if (is_bool($lock)) {
1014
            return $lock ? ' FOR UPDATE ' : '';
1015
        }
1016
1017
        if (is_string($lock) && !empty($lock)) {
1018
            return ' ' . trim($lock) . ' ';
1019
        } else {
1020
            return '';
1021
        }
1022
    }
1023
1024
    /**
1025
     * 生成查询SQL
1026
     * @access public
1027
     * @param  Query  $query  查询对象
1028
     * @param  bool   $one    是否仅获取一个记录
1029
     * @return string
1030
     */
1031
    public function select(Query $query, bool $one = false): string
1032
    {
1033
        $options = $query->getOptions();
1034
1035
        return str_replace(
1036
            ['%TABLE%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
1037
            [
1038
                $this->parseTable($query, $options['table']),
1039
                $this->parseDistinct($query, $options['distinct']),
1040
                $this->parseExtra($query, $options['extra']),
1041
                $this->parseField($query, $options['field']),
1042
                $this->parseJoin($query, $options['join']),
1043
                $this->parseWhere($query, $options['where']),
1044
                $this->parseGroup($query, $options['group']),
1045
                $this->parseHaving($query, $options['having']),
1046
                $this->parseOrder($query, $options['order']),
1047
                $this->parseLimit($query, $one ? '1' : $options['limit']),
1048
                $this->parseUnion($query, $options['union']),
1049
                $this->parseLock($query, $options['lock']),
1050
                $this->parseComment($query, $options['comment']),
1051
                $this->parseForce($query, $options['force']),
1052
            ],
1053
            $this->selectSql);
1054
    }
1055
1056
    /**
1057
     * 生成Insert SQL
1058
     * @access public
1059
     * @param  Query     $query   查询对象
1060
     * @param  bool      $replace 是否replace
1061
     * @return string
1062
     */
1063
    public function insert(Query $query, bool $replace = false): string
1064
    {
1065
        $options = $query->getOptions();
1066
1067
        // 分析并处理数据
1068
        $data = $this->parseData($query, $options['data']);
1069
        if (empty($data)) {
1070
            return '';
1071
        }
1072
1073
        $fields = array_keys($data);
1074
        $values = array_values($data);
1075
1076
        return str_replace(
1077
            ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
1078
            [
1079
                $replace ? 'REPLACE' : 'INSERT',
1080
                $this->parseTable($query, $options['table']),
1081
                $this->parseExtra($query, $options['extra']),
1082
                implode(' , ', $fields),
1083
                implode(' , ', $values),
1084
                $this->parseComment($query, $options['comment']),
1085
            ],
1086
            $this->insertSql);
1087
    }
1088
1089
    /**
1090
     * 生成insertall SQL
1091
     * @access public
1092
     * @param  Query     $query   查询对象
1093
     * @param  array     $dataSet 数据集
1094
     * @param  bool      $replace 是否replace
1095
     * @return string
1096
     */
1097
    public function insertAll(Query $query, array $dataSet, bool $replace = false): string
1098
    {
1099
        $options = $query->getOptions();
1100
1101
        // 获取合法的字段
1102
        if ('*' == $options['field']) {
1103
            $allowFields = $this->connection->getTableFields($options['table']);
1104
        } else {
1105
            $allowFields = $options['field'];
1106
        }
1107
1108
        // 获取绑定信息
1109
        $bind = $query->getFieldsBindType();
1110
1111
        foreach ($dataSet as $k => $data) {
1112
            $data = $this->parseData($query, $data, $allowFields, $bind, '_' . $k);
0 ignored issues
show
Unused Code introduced by
The call to think\db\Builder::parseData() has too many arguments starting with '_' . $k. ( Ignorable by Annotation )

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

1112
            /** @scrutinizer ignore-call */ 
1113
            $data = $this->parseData($query, $data, $allowFields, $bind, '_' . $k);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1113
1114
            $values[] = 'SELECT ' . implode(',', array_values($data));
1115
1116
            if (!isset($insertFields)) {
1117
                $insertFields = array_keys($data);
1118
            }
1119
        }
1120
1121
        $fields = [];
1122
1123
        foreach ($insertFields as $field) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $insertFields seems to be defined by a foreach iteration on line 1111. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1124
            $fields[] = $this->parseKey($query, $field);
1125
        }
1126
1127
        return str_replace(
1128
            ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
1129
            [
1130
                $replace ? 'REPLACE' : 'INSERT',
1131
                $this->parseTable($query, $options['table']),
1132
                $this->parseExtra($query, $options['extra']),
1133
                implode(' , ', $fields),
1134
                implode(' UNION ALL ', $values),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $values seems to be defined by a foreach iteration on line 1111. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1135
                $this->parseComment($query, $options['comment']),
1136
            ],
1137
            $this->insertAllSql);
1138
    }
1139
1140
    /**
1141
     * 生成slect insert SQL
1142
     * @access public
1143
     * @param  Query     $query  查询对象
1144
     * @param  array     $fields 数据
1145
     * @param  string    $table  数据表
1146
     * @return string
1147
     */
1148
    public function selectInsert(Query $query, array $fields, string $table): string
1149
    {
1150
        foreach ($fields as &$field) {
1151
            $field = $this->parseKey($query, $field, true);
1152
        }
1153
1154
        return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query);
1155
    }
1156
1157
    /**
1158
     * 生成update SQL
1159
     * @access public
1160
     * @param  Query     $query  查询对象
1161
     * @return string
1162
     */
1163
    public function update(Query $query): string
1164
    {
1165
        $options = $query->getOptions();
1166
1167
        $table = $this->parseTable($query, $options['table']);
0 ignored issues
show
Unused Code introduced by
The assignment to $table is dead and can be removed.
Loading history...
1168
        $data  = $this->parseData($query, $options['data']);
1169
1170
        if (empty($data)) {
1171
            return '';
1172
        }
1173
1174
        foreach ($data as $key => $val) {
1175
            $set[] = $key . ' = ' . $val;
1176
        }
1177
1178
        return str_replace(
1179
            ['%TABLE%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
1180
            [
1181
                $this->parseTable($query, $options['table']),
1182
                $this->parseExtra($query, $options['extra']),
1183
                implode(' , ', $set),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $set seems to be defined by a foreach iteration on line 1174. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1184
                $this->parseJoin($query, $options['join']),
1185
                $this->parseWhere($query, $options['where']),
1186
                $this->parseOrder($query, $options['order']),
1187
                $this->parseLimit($query, $options['limit']),
1188
                $this->parseLock($query, $options['lock']),
1189
                $this->parseComment($query, $options['comment']),
1190
            ],
1191
            $this->updateSql);
1192
    }
1193
1194
    /**
1195
     * 生成delete SQL
1196
     * @access public
1197
     * @param  Query  $query  查询对象
1198
     * @return string
1199
     */
1200
    public function delete(Query $query): string
1201
    {
1202
        $options = $query->getOptions();
1203
1204
        return str_replace(
1205
            ['%TABLE%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
1206
            [
1207
                $this->parseTable($query, $options['table']),
1208
                $this->parseExtra($query, $options['extra']),
1209
                !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '',
1210
                $this->parseJoin($query, $options['join']),
1211
                $this->parseWhere($query, $options['where']),
1212
                $this->parseOrder($query, $options['order']),
1213
                $this->parseLimit($query, $options['limit']),
1214
                $this->parseLock($query, $options['lock']),
1215
                $this->parseComment($query, $options['comment']),
1216
            ],
1217
            $this->deleteSql);
1218
    }
1219
}
1220