Passed
Push — 5.2 ( 648d84...942813 )
by liu
02:36
created

Query::data()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
1 ignored issue
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
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 Closure;
16
use PDO;
17
use PDOStatement;
18
use think\App;
19
use think\Collection;
20
use think\Container;
21
use think\db\exception\BindParamException;
22
use think\db\exception\DataNotFoundException;
23
use think\db\exception\ModelNotFoundException;
24
use think\Exception;
25
use think\exception\DbException;
26
use think\exception\PDOException;
27
use think\facade\Db;
28
use think\Model;
29
use think\model\Collection as ModelCollection;
30
use think\model\Relation;
31
use think\model\relation\OneToOne;
32
33
class Query
1 ignored issue
show
Coding Style introduced by
Missing class doc comment
Loading history...
34
{
35
    /**
36
     * 当前数据库连接对象
37
     * @var Connection
38
     */
39
    protected $connection;
40
41
    /**
42
     * 当前模型对象
43
     * @var Model
44
     */
45
    protected $model;
46
47
    /**
48
     * 当前数据表名称(不含前缀)
49
     * @var string
50
     */
51
    protected $name = '';
52
53
    /**
54
     * 当前数据表主键
55
     * @var string|array
56
     */
57
    protected $pk;
58
59
    /**
60
     * 当前数据表前缀
61
     * @var string
62
     */
63
    protected $prefix = '';
64
65
    /**
66
     * 当前查询参数
67
     * @var array
68
     */
69
    protected $options = [];
70
71
    /**
72
     * 分页查询配置
73
     * @var array
74
     */
75
    protected $paginateConfig = [
76
        'query'     => [], //url额外参数
77
        'fragment'  => '', //url锚点
78
        'type'      => 'bootstrap', //分页类名
79
        'var_page'  => 'page', //分页变量
80
        'list_rows' => 15, //每页数量
81
    ];
82
83
    /**
84
     * 当前参数绑定
85
     * @var array
86
     */
87
    protected $bind = [];
88
89
    /**
90
     * 事件回调
91
     * @var array
92
     */
93
    protected static $event = [];
94
95
    /**
96
     * 日期查询表达式
97
     * @var array
98
     */
99
    protected $timeRule = [
100
        'today'      => ['today', 'tomorrow'],
101
        'yesterday'  => ['yesterday', 'today'],
102
        'week'       => ['this week 00:00:00', 'next week 00:00:00'],
103
        'last week'  => ['last week 00:00:00', 'this week 00:00:00'],
104
        'month'      => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'],
105
        'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'],
106
        'year'       => ['this year 1/1', 'next year 1/1'],
107
        'last year'  => ['last year 1/1', 'this year 1/1'],
108
    ];
109
110
    /**
111
     * 架构函数
112
     * @access public
113
     * @param  Connection      $connection 数据库连接对象
114
     */
115
    public function __construct(Connection $connection)
116
    {
117
        $this->connection = $connection;
118
119
        $this->prefix = $this->connection->getConfig('prefix');
120
    }
121
122
    /**
123
     * 创建一个新的查询对象
124
     * @access public
125
     * @return Query
126
     */
127
    public function newQuery()
128
    {
129
        return new static($this->connection);
130
    }
131
132
    /**
133
     * 利用__call方法实现一些特殊的Model方法
134
     * @access public
135
     * @param  string $method 方法名称
136
     * @param  array  $args   调用参数
137
     * @return mixed
138
     * @throws DbException
139
     * @throws Exception
140
     */
141
    public function __call(string $method, array $args)
142
    {
143
        if (strtolower(substr($method, 0, 5)) == 'getby') {
144
            // 根据某个字段获取记录
145
            $field = App::parseName(substr($method, 5));
146
            return $this->where($field, '=', $args[0])->find();
147
        } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
148
            // 根据某个字段获取记录的某个值
149
            $name = App::parseName(substr($method, 10));
150
            return $this->where($name, '=', $args[0])->value($args[1]);
151
        } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
152
            $name = App::parseName(substr($method, 7));
153
            array_unshift($args, $name);
154
            return call_user_func_array([$this, 'whereOr'], $args);
155
        } elseif (strtolower(substr($method, 0, 5)) == 'where') {
156
            $name = App::parseName(substr($method, 5));
157
            array_unshift($args, $name);
158
            return call_user_func_array([$this, 'where'], $args);
159
        } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
160
            // 动态调用命名范围
161
            $method = 'scope' . $method;
162
            array_unshift($args, $this);
163
164
            call_user_func_array([$this->model, $method], $args);
165
            return $this;
166
        } else {
167
            throw new Exception('method not exist:' . static::class . '->' . $method);
168
        }
169
    }
170
171
    /**
172
     * 获取当前的数据库Connection对象
173
     * @access public
174
     * @return Connection
175
     */
176
    public function getConnection()
177
    {
178
        return $this->connection;
179
    }
180
181
    /**
182
     * 设置当前的数据库Connection对象
183
     * @access public
184
     * @param  Connection      $connection 数据库连接对象
185
     * @return $this
186
     */
187
    public function setConnection($connection)
188
    {
189
        $this->connection = $connection;
190
191
        return $this;
192
    }
193
194
    /**
195
     * 指定模型
196
     * @access public
197
     * @param  Model $model 模型对象实例
198
     * @return $this
199
     */
200
    public function model(Model $model)
201
    {
202
        $this->model = $model;
203
        return $this;
204
    }
205
206
    /**
207
     * 获取当前的模型对象
208
     * @access public
209
     * @return Model|null
210
     */
211
    public function getModel()
212
    {
213
        return $this->model ? $this->model->setQuery($this) : null;
214
    }
215
216
    /**
217
     * 指定当前数据表名(不含前缀)
218
     * @access public
219
     * @param  string $name 不含前缀的数据表名字
220
     * @return $this
221
     */
222
    public function name(string $name)
223
    {
224
        $this->name = $name;
225
        return $this;
226
    }
227
228
    /**
229
     * 获取当前的数据表名称
230
     * @access public
231
     * @return string
232
     */
233
    public function getName(): string
234
    {
235
        return $this->name ?: $this->model->getName();
236
    }
237
238
    /**
239
     * 获取数据库的配置参数
240
     * @access public
241
     * @param  string $name 参数名称
242
     * @return mixed
243
     */
244
    public function getConfig(string $name = '')
245
    {
246
        return $this->connection->getConfig($name);
247
    }
248
249
    /**
250
     * 得到当前或者指定名称的数据表
251
     * @access public
252
     * @param  string $name 不含前缀的数据表名字
253
     * @return string
254
     */
255
    public function getTable(string $name = ''): string
256
    {
257
        if (empty($name) && isset($this->options['table'])) {
258
            return $this->options['table'];
259
        }
260
261
        $name = $name ?: $this->name;
262
263
        return $this->prefix . App::parseName($name);
264
    }
265
266
    /**
267
     * 获取数据表字段信息
268
     * @access public
269
     * @param  string $tableName 数据表名
270
     * @return array
271
     */
272
    public function getTableFields($tableName = ''): array
273
    {
274
        if ('' == $tableName) {
275
            $tableName = $this->options['table'] ?? $this->getTable();
276
        }
277
278
        return $this->connection->getTableFields($tableName);
279
    }
280
281
    /**
282
     * 设置字段类型信息
283
     * @access public
284
     * @param  array $type 字段类型信息
285
     * @return $this
286
     */
287
    public function setFieldType(array $type)
288
    {
289
        $this->options['field_type'] = $type;
290
        return $this;
291
    }
292
293
    /**
294
     * 获取字段类型信息
295
     * @access public
296
     * @return array
297
     */
298
    public function getFieldsType(): array
299
    {
300
        if (!empty($this->options['field_type'])) {
301
            return $this->options['field_type'];
302
        }
303
304
        $tableName = $this->options['table'] ?? $this->getTable();
305
306
        return $this->connection->getFieldsType($tableName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection...tFieldsType($tableName) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
307
    }
308
309
    /**
310
     * 获取字段类型信息
311
     * @access public
312
     * @param  string $field 字段名
313
     * @return string|null
314
     */
315
    public function getFieldType(string $field)
316
    {
317
        $fieldType = $this->getFieldsType();
318
319
        return $fieldType[$field] ?? null;
320
    }
321
322
    /**
323
     * 获取字段类型信息
324
     * @access public
325
     * @return array
326
     */
327
    public function getFieldsBindType(): array
328
    {
329
        $fieldType = $this->getFieldsType();
330
331
        return array_map([$this->connection, 'getFieldBindType'], $fieldType);
332
    }
333
334
    /**
335
     * 获取字段类型信息
336
     * @access public
337
     * @param  string $field 字段名
338
     * @return int
339
     */
340
    public function getFieldBindType(string $field): int
341
    {
342
        $fieldType = $this->getFieldType($field);
343
344
        return $this->connection->getFieldBindType($fieldType ?: '');
345
    }
346
347
    /**
348
     * 执行查询 返回数据集
349
     * @access public
350
     * @param  string $sql    sql指令
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 4 found
Loading history...
351
     * @param  array  $bind   参数绑定
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
352
     * @return array
353
     * @throws BindParamException
354
     * @throws PDOException
355
     */
356
    public function query(string $sql, array $bind = []): array
357
    {
358
        return $this->connection->query($this, $sql, $bind, true);
359
    }
360
361
    /**
362
     * 执行语句
363
     * @access public
364
     * @param  string $sql  sql指令
365
     * @param  array  $bind 参数绑定
366
     * @return int
367
     * @throws BindParamException
368
     * @throws PDOException
369
     */
370
    public function execute(string $sql, array $bind = []): int
371
    {
372
        return $this->connection->execute($this, $sql, $bind, true);
373
    }
374
375
    /**
376
     * 监听SQL执行
377
     * @access public
378
     * @param  callable $callback 回调方法
379
     * @return void
380
     */
381
    public function listen(callable $callback): void
382
    {
383
        $this->connection->listen($callback);
384
    }
385
386
    /**
387
     * 获取最近插入的ID
388
     * @access public
389
     * @param  string $sequence 自增序列名
390
     * @return string
391
     */
392
    public function getLastInsID(string $sequence = null): string
393
    {
394
        return $this->connection->getLastInsID($sequence);
395
    }
396
397
    /**
398
     * 获取返回或者影响的记录数
399
     * @access public
400
     * @return integer
401
     */
402
    public function getNumRows(): int
403
    {
404
        return $this->connection->getNumRows();
405
    }
406
407
    /**
408
     * 获取最近一次查询的sql语句
409
     * @access public
410
     * @return string
411
     */
412
    public function getLastSql(): string
413
    {
414
        return $this->connection->getLastSql();
415
    }
416
417
    /**
418
     * 执行数据库事务
419
     * @access public
420
     * @param  callable $callback 数据操作方法回调
421
     * @return mixed
422
     */
423
    public function transaction(callable $callback)
424
    {
425
        return $this->connection->transaction($callback);
426
    }
427
428
    /**
429
     * 启动事务
430
     * @access public
431
     * @return void
432
     */
433
    public function startTrans(): void
434
    {
435
        $this->connection->startTrans();
436
    }
437
438
    /**
439
     * 用于非自动提交状态下面的查询提交
440
     * @access public
441
     * @return void
442
     * @throws PDOException
443
     */
444
    public function commit(): void
445
    {
446
        $this->connection->commit();
447
    }
448
449
    /**
450
     * 事务回滚
451
     * @access public
452
     * @return void
453
     * @throws PDOException
454
     */
455
    public function rollback(): void
456
    {
457
        $this->connection->rollback();
458
    }
459
460
    /**
461
     * 批处理执行SQL语句
462
     * 批处理的指令都认为是execute操作
463
     * @access public
464
     * @param  array $sql SQL批处理指令
465
     * @return bool
466
     */
467
    public function batchQuery(array $sql = []): bool
468
    {
469
        return $this->connection->batchQuery($this, $sql);
470
    }
471
472
    /**
473
     * 得到某个字段的值
474
     * @access public
475
     * @param  string $field   字段名
476
     * @param  mixed  $default 默认值
477
     * @return mixed
478
     */
479
    public function value(string $field, $default = null)
480
    {
481
        return $this->connection->value($this, $field, $default);
482
    }
483
484
    /**
485
     * 得到某个列的数组
486
     * @access public
487
     * @param  string $field 字段名 多个字段用逗号分隔
488
     * @param  string $key   索引
489
     * @return array
490
     */
491
    public function column(string $field, string $key = ''): array
492
    {
493
        return $this->connection->column($this, $field, $key);
494
    }
495
496
    /**
497
     * 聚合查询
498
     * @access protected
499
     * @param  string     $aggregate    聚合方法
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
500
     * @param  string|Raw $field        字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
501
     * @param  bool       $force        强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
502
     * @return mixed
503
     */
504
    protected function aggregate(string $aggregate, $field, bool $force = false)
505
    {
506
        return $this->connection->aggregate($this, $aggregate, $field, $force);
507
    }
508
509
    /**
510
     * COUNT查询
511
     * @access public
512
     * @param  string|Raw $field 字段名
513
     * @return int
514
     */
515
    public function count(string $field = '*'): int
516
    {
517
        if (!empty($this->options['group'])) {
518
            // 支持GROUP
519
            $options = $this->getOptions();
520
            $subSql  = $this->options($options)->field('count(' . $field . ') AS think_count')->bind($this->bind)->buildSql();
521
522
            $query = $this->newQuery()->table([$subSql => '_group_count_']);
523
524
            $count = $query->aggregate('COUNT', '*');
525
        } else {
526
            $count = $this->aggregate('COUNT', $field);
527
        }
528
529
        return (int) $count;
530
    }
531
532
    /**
533
     * SUM查询
534
     * @access public
535
     * @param  string|Raw $field 字段名
536
     * @return float
537
     */
538
    public function sum($field): float
539
    {
540
        return $this->aggregate('SUM', $field, true);
541
    }
542
543
    /**
544
     * MIN查询
545
     * @access public
546
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
547
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
548
     * @return mixed
549
     */
550
    public function min($field, bool $force = true)
551
    {
552
        return $this->aggregate('MIN', $field, $force);
553
    }
554
555
    /**
556
     * MAX查询
557
     * @access public
558
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
559
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
560
     * @return mixed
561
     */
562
    public function max($field, bool $force = true)
563
    {
564
        return $this->aggregate('MAX', $field, $force);
565
    }
566
567
    /**
568
     * AVG查询
569
     * @access public
570
     * @param  string|Raw $field 字段名
571
     * @return float
572
     */
573
    public function avg($field): float
574
    {
575
        return $this->aggregate('AVG', $field, true);
576
    }
577
578
    /**
579
     * 查询SQL组装 join
580
     * @access public
581
     * @param  mixed  $join      关联的表名
582
     * @param  mixed  $condition 条件
583
     * @param  string $type      JOIN类型
584
     * @param  array  $bind      参数绑定
585
     * @return $this
586
     */
587
    public function join($join, string $condition = null, string $type = 'INNER', array $bind = [])
588
    {
589
        $table = $this->getJoinTable($join);
590
591
        if (!empty($bind) && $condition) {
592
            $this->bindParams($condition, $bind);
593
        }
594
595
        $this->options['join'][] = [$table, strtoupper($type), $condition];
596
597
        return $this;
598
    }
599
600
    /**
601
     * LEFT JOIN
602
     * @access public
603
     * @param  mixed  $join      关联的表名
604
     * @param  mixed  $condition 条件
605
     * @param  array  $bind      参数绑定
606
     * @return $this
607
     */
608
    public function leftJoin($join, string $condition = null, array $bind = [])
609
    {
610
        return $this->join($join, $condition, 'LEFT', $bind);
611
    }
612
613
    /**
614
     * RIGHT JOIN
615
     * @access public
616
     * @param  mixed  $join      关联的表名
617
     * @param  mixed  $condition 条件
618
     * @param  array  $bind      参数绑定
619
     * @return $this
620
     */
621
    public function rightJoin($join, string $condition = null, array $bind = [])
622
    {
623
        return $this->join($join, $condition, 'RIGHT', $bind);
624
    }
625
626
    /**
627
     * FULL JOIN
628
     * @access public
629
     * @param  mixed  $join      关联的表名
630
     * @param  mixed  $condition 条件
631
     * @param  array  $bind      参数绑定
632
     * @return $this
633
     */
634
    public function fullJoin($join, string $condition = null, array $bind = [])
635
    {
636
        return $this->join($join, $condition, 'FULL');
637
    }
638
639
    /**
640
     * 获取Join表名及别名 支持
641
     * ['prefix_table或者子查询'=>'alias'] 'table alias'
642
     * @access protected
643
     * @param  array|string|Raw $join  JION表名
644
     * @param  string           $alias 别名
645
     * @return string|array
646
     */
647
    protected function getJoinTable($join, &$alias = null)
648
    {
649
        if (is_array($join)) {
650
            $table = $join;
651
            $alias = array_shift($join);
652
            return $table;
653
        } elseif ($join instanceof Raw) {
654
            return $join;
655
        }
656
657
        $join = trim($join);
658
659
        if (false !== strpos($join, '(')) {
660
            // 使用子查询
661
            $table = $join;
662
        } else {
663
            // 使用别名
664
            if (strpos($join, ' ')) {
665
                // 使用别名
666
                list($table, $alias) = explode(' ', $join);
667
            } else {
668
                $table = $join;
669
                if (false === strpos($join, '.')) {
670
                    $alias = $join;
671
                }
672
            }
673
674
            if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) {
675
                $table = $this->getTable($table);
676
            }
677
        }
678
679
        if (!empty($alias) && $table != $alias) {
680
            $table = [$table => $alias];
681
        }
682
683
        return $table;
684
    }
685
686
    /**
687
     * 查询SQL组装 union
688
     * @access public
689
     * @param  mixed   $union UNION
690
     * @param  boolean $all   是否适用UNION ALL
691
     * @return $this
692
     */
693
    public function union($union, bool $all = false)
694
    {
695
        $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
696
697
        if (is_array($union)) {
698
            $this->options['union'] = array_merge($this->options['union'], $union);
699
        } else {
700
            $this->options['union'][] = $union;
701
        }
702
703
        return $this;
704
    }
705
706
    /**
707
     * 查询SQL组装 union all
708
     * @access public
709
     * @param  mixed   $union UNION数据
710
     * @return $this
711
     */
712
    public function unionAll($union)
713
    {
714
        return $this->union($union, true);
715
    }
716
717
    /**
718
     * 指定查询字段 支持字段排除和指定数据表
719
     * @access public
720
     * @param  mixed   $field     字段信息
721
     * @param  boolean $except    是否排除
722
     * @param  string  $tableName 数据表名
723
     * @param  string  $prefix    字段前缀
724
     * @param  string  $alias     别名前缀
725
     * @return $this
726
     */
727
    public function field($field, bool $except = false, string $tableName = '', string $prefix = '', string $alias = '')
728
    {
729
        if (empty($field)) {
730
            return $this;
731
        } elseif ($field instanceof Raw) {
732
            $this->options['field'][] = $field;
733
            return $this;
734
        }
735
736
        if (is_string($field)) {
737
            if (preg_match('/[\<\'\"\(]/', $field)) {
738
                return $this->fieldRaw($field);
739
            }
740
741
            $field = array_map('trim', explode(',', $field));
742
        }
743
744
        if (true === $field) {
745
            // 获取全部字段
746
            $fields = $this->getTableFields($tableName);
747
            $field  = $fields ?: ['*'];
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
748
        } elseif ($except) {
749
            // 字段排除
750
            $fields = $this->getTableFields($tableName);
751
            $field  = $fields ? array_diff($fields, $field) : $field;
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
752
        }
753
754
        if ($tableName) {
755
            // 添加统一的前缀
756
            $prefix = $prefix ?: $tableName;
757
            foreach ($field as $key => &$val) {
758
                if (is_numeric($key) && $alias) {
759
                    $field[$prefix . '.' . $val] = $alias . $val;
760
                    unset($field[$key]);
761
                } elseif (is_numeric($key)) {
762
                    $val = $prefix . '.' . $val;
763
                }
764
            }
765
        }
766
767
        if (isset($this->options['field'])) {
768
            $field = array_merge((array) $this->options['field'], $field);
769
        }
770
771
        $this->options['field'] = array_unique($field);
772
773
        return $this;
774
    }
775
776
    /**
777
     * 表达式方式指定查询字段
778
     * @access public
779
     * @param  string $field 字段名
780
     * @return $this
781
     */
782
    public function fieldRaw(string $field)
783
    {
784
        $this->options['field'][] = new Raw($field);
785
786
        return $this;
787
    }
788
789
    /**
790
     * 设置数据
791
     * @access public
792
     * @param  array $data 数据
793
     * @return $this
794
     */
795
    public function data(array $data)
796
    {
797
        $this->options['data'] = $data;
798
799
        return $this;
800
    }
801
802
    /**
803
     * 字段值增长
804
     * @access public
805
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
806
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
807
     * @param  integer $lazyTime 延时时间(s)
808
     * @param  string  $op INC/DEC
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 1 found
Loading history...
809
     * @return $this
810
     */
811
    public function inc(string $field, int $step = 1, int $lazyTime = 0, string $op = 'INC')
812
    {
813
        if ($lazyTime > 0) {
814
            // 延迟写入
815
            $condition = $this->options['where'] ?? [];
816
817
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
818
            $step = $this->connection->lazyWrite($op, $guid, $step, $lazyTime);
819
820
            if (false === $step) {
821
                return $this;
822
            }
823
824
            $op = 'INC';
825
        }
826
827
        $this->options['data'][$field] = [$op, $step];
828
829
        return $this;
830
    }
831
832
    /**
833
     * 字段值减少
834
     * @access public
835
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
836
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
837
     * @param  integer $lazyTime 延时时间(s)
838
     * @return $this
839
     */
840
    public function dec(string $field, int $step = 1, int $lazyTime = 0)
841
    {
842
        return $this->inc($field, $step, $lazyTime, 'DEC');
843
    }
844
845
    /**
846
     * 使用表达式设置数据
847
     * @access public
848
     * @param  string $field 字段名
849
     * @param  string $value 字段值
850
     * @return $this
851
     */
852
    public function exp(string $field, string $value)
853
    {
854
        $this->options['data'][$field] = new Raw($value);
855
        return $this;
856
    }
857
858
    /**
859
     * 指定JOIN查询字段
860
     * @access public
861
     * @param  string|array $join  数据表
862
     * @param  string|array $field 查询字段
863
     * @param  string       $on    JOIN条件
864
     * @param  string       $type  JOIN类型
865
     * @param  array        $bind  参数绑定
866
     * @return $this
867
     */
868
    public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = [])
869
    {
870
        $this->options['view'] = true;
871
872
        $fields = [];
873
        $table  = $this->getJoinTable($join, $alias);
874
875
        if (true === $field) {
876
            $fields = $alias . '.*';
877
        } else {
878
            if (is_string($field)) {
879
                $field = explode(',', $field);
880
            }
881
882
            foreach ($field as $key => $val) {
883
                if (is_numeric($key)) {
884
                    $fields[] = $alias . '.' . $val;
885
886
                    $this->options['map'][$val] = $alias . '.' . $val;
887
                } else {
888
                    if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
889
                        $name = $key;
890
                    } else {
891
                        $name = $alias . '.' . $key;
892
                    }
893
894
                    $fields[] = $name . ' AS ' . $val;
895
896
                    $this->options['map'][$val] = $name;
897
                }
898
            }
899
        }
900
901
        $this->field($fields);
902
903
        if ($on) {
904
            $this->join($table, $on, $type, $bind);
905
        } else {
906
            $this->table($table);
907
        }
908
909
        return $this;
910
    }
911
912
    /**
913
     * 指定AND查询条件
914
     * @access public
915
     * @param  mixed $field     查询字段
916
     * @param  mixed $op        查询表达式
917
     * @param  mixed $condition 查询条件
918
     * @return $this
919
     */
920
    public function where($field, $op = null, $condition = null)
921
    {
922
        if ($field instanceof $this) {
923
            $this->options['where'] = $field->getOptions('where');
924
            return $this;
925
        }
926
927
        $param = func_get_args();
928
        array_shift($param);
929
        return $this->parseWhereExp('AND', $field, $op, $condition, $param);
930
    }
931
932
    /**
933
     * 指定OR查询条件
934
     * @access public
935
     * @param  mixed $field     查询字段
936
     * @param  mixed $op        查询表达式
937
     * @param  mixed $condition 查询条件
938
     * @return $this
939
     */
940
    public function whereOr($field, $op = null, $condition = null)
941
    {
942
        $param = func_get_args();
943
        array_shift($param);
944
        return $this->parseWhereExp('OR', $field, $op, $condition, $param);
945
    }
946
947
    /**
948
     * 指定XOR查询条件
949
     * @access public
950
     * @param  mixed $field     查询字段
951
     * @param  mixed $op        查询表达式
952
     * @param  mixed $condition 查询条件
953
     * @return $this
954
     */
955
    public function whereXor($field, $op = null, $condition = null)
956
    {
957
        $param = func_get_args();
958
        array_shift($param);
959
        return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
960
    }
961
962
    /**
963
     * 指定Null查询条件
964
     * @access public
965
     * @param  mixed  $field 查询字段
966
     * @param  string $logic 查询逻辑 and or xor
967
     * @return $this
968
     */
969
    public function whereNull(string $field, string $logic = 'AND')
970
    {
971
        return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
972
    }
973
974
    /**
975
     * 指定NotNull查询条件
976
     * @access public
977
     * @param  mixed  $field 查询字段
978
     * @param  string $logic 查询逻辑 and or xor
979
     * @return $this
980
     */
981
    public function whereNotNull(string $field, string $logic = 'AND')
982
    {
983
        return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
984
    }
985
986
    /**
987
     * 指定Exists查询条件
988
     * @access public
989
     * @param  mixed  $condition 查询条件
990
     * @param  string $logic     查询逻辑 and or xor
991
     * @return $this
992
     */
993
    public function whereExists($condition, string $logic = 'AND')
994
    {
995
        if (is_string($condition)) {
996
            $condition = new Raw($condition);
997
        }
998
999
        $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
1000
        return $this;
1001
    }
1002
1003
    /**
1004
     * 指定NotExists查询条件
1005
     * @access public
1006
     * @param  mixed  $condition 查询条件
1007
     * @param  string $logic     查询逻辑 and or xor
1008
     * @return $this
1009
     */
1010
    public function whereNotExists($condition, string $logic = 'AND')
1011
    {
1012
        if (is_string($condition)) {
1013
            $condition = new Raw($condition);
1014
        }
1015
1016
        $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
1017
        return $this;
1018
    }
1019
1020
    /**
1021
     * 指定In查询条件
1022
     * @access public
1023
     * @param  mixed  $field     查询字段
1024
     * @param  mixed  $condition 查询条件
1025
     * @param  string $logic     查询逻辑 and or xor
1026
     * @return $this
1027
     */
1028
    public function whereIn(string $field, $condition, string $logic = 'AND')
1029
    {
1030
        return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
1031
    }
1032
1033
    /**
1034
     * 指定NotIn查询条件
1035
     * @access public
1036
     * @param  mixed  $field     查询字段
1037
     * @param  mixed  $condition 查询条件
1038
     * @param  string $logic     查询逻辑 and or xor
1039
     * @return $this
1040
     */
1041
    public function whereNotIn(string $field, $condition, string $logic = 'AND')
1042
    {
1043
        return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
1044
    }
1045
1046
    /**
1047
     * 指定Like查询条件
1048
     * @access public
1049
     * @param  mixed  $field     查询字段
1050
     * @param  mixed  $condition 查询条件
1051
     * @param  string $logic     查询逻辑 and or xor
1052
     * @return $this
1053
     */
1054
    public function whereLike(string $field, $condition, string $logic = 'AND')
1055
    {
1056
        return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
1057
    }
1058
1059
    /**
1060
     * 指定NotLike查询条件
1061
     * @access public
1062
     * @param  mixed  $field     查询字段
1063
     * @param  mixed  $condition 查询条件
1064
     * @param  string $logic     查询逻辑 and or xor
1065
     * @return $this
1066
     */
1067
    public function whereNotLike(string $field, $condition, string $logic = 'AND')
1068
    {
1069
        return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
1070
    }
1071
1072
    /**
1073
     * 指定Between查询条件
1074
     * @access public
1075
     * @param  mixed  $field     查询字段
1076
     * @param  mixed  $condition 查询条件
1077
     * @param  string $logic     查询逻辑 and or xor
1078
     * @return $this
1079
     */
1080
    public function whereBetween(string $field, $condition, string $logic = 'AND')
1081
    {
1082
        return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
1083
    }
1084
1085
    /**
1086
     * 指定NotBetween查询条件
1087
     * @access public
1088
     * @param  mixed  $field     查询字段
1089
     * @param  mixed  $condition 查询条件
1090
     * @param  string $logic     查询逻辑 and or xor
1091
     * @return $this
1092
     */
1093
    public function whereNotBetween(string $field, $condition, string $logic = 'AND')
1094
    {
1095
        return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
1096
    }
1097
1098
    /**
1099
     * 指定FIND_IN_SET查询条件
1100
     * @access public
1101
     * @param  mixed  $field     查询字段
1102
     * @param  mixed  $condition 查询条件
1103
     * @param  string $logic     查询逻辑 and or xor
1104
     * @return $this
1105
     */
1106
    public function whereFindInSet(string $field, $condition, string $logic = 'AND')
1107
    {
1108
        return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true);
1109
    }
1110
1111
    /**
1112
     * 比较两个字段
1113
     * @access public
1114
     * @param  string $field1     查询字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1115
     * @param  string $operator   比较操作符
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
1116
     * @param  string $field2     比较字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1117
     * @param  string $logic      查询逻辑 and or xor
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 6 found
Loading history...
1118
     * @return $this
1119
     */
1120
    public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
1121
    {
1122
        if (is_null($field2)) {
1123
            $field2   = $operator;
1124
            $operator = '=';
1125
        }
1126
1127
        return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
1128
    }
1129
1130
    /**
1131
     * 设置软删除字段及条件
1132
     * @access public
1133
     * @param  string       $field     查询字段
1134
     * @param  mixed        $condition 查询条件
1135
     * @return $this
1136
     */
1137
    public function useSoftDelete(string $field, $condition = null)
1138
    {
1139
        if ($field) {
1140
            $this->options['soft_delete'] = [$field, $condition];
1141
        }
1142
1143
        return $this;
1144
    }
1145
1146
    /**
1147
     * 指定Exp查询条件
1148
     * @access public
1149
     * @param  mixed  $field 查询字段
1150
     * @param  string $where 查询条件
1151
     * @param  array  $bind  参数绑定
1152
     * @param  string $logic 查询逻辑 and or xor
1153
     * @return $this
1154
     */
1155
    public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
1156
    {
1157
        if (!empty($bind)) {
1158
            $this->bindParams($where, $bind);
1159
        }
1160
1161
        $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where)];
1162
1163
        return $this;
1164
    }
1165
1166
    /**
1167
     * 指定字段Raw查询
1168
     * @access public
1169
     * @param  string $field     查询字段表达式
1170
     * @param  mixed  $op        查询表达式
1171
     * @param  string $condition 查询条件
1172
     * @param  string $logic     查询逻辑 and or xor
1173
     * @return $this
1174
     */
1175
    public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
1176
    {
1177
        if (is_null($condition)) {
1178
            $condition = $op;
1179
            $op        = '=';
1180
        }
1181
1182
        $this->options['where'][$logic][] = [new Raw($field), $op, $condition];
1183
        return $this;
1184
    }
1185
1186
    /**
1187
     * 指定表达式查询条件
1188
     * @access public
1189
     * @param  string $where 查询条件
1190
     * @param  array  $bind  参数绑定
1191
     * @param  string $logic 查询逻辑 and or xor
1192
     * @return $this
1193
     */
1194
    public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
1195
    {
1196
        if (!empty($bind)) {
1197
            $this->bindParams($where, $bind);
1198
        }
1199
1200
        $this->options['where'][$logic][] = new Raw($where);
1201
1202
        return $this;
1203
    }
1204
1205
    /**
1206
     * 指定表达式查询条件 OR
1207
     * @access public
1208
     * @param  string $where 查询条件
1209
     * @param  array  $bind  参数绑定
1210
     * @return $this
1211
     */
1212
    public function whereOrRaw(string $where, array $bind = [])
1213
    {
1214
        return $this->whereRaw($where, $bind, 'OR');
1215
    }
1216
1217
    /**
1218
     * 分析查询表达式
1219
     * @access protected
1220
     * @param  string $logic     查询逻辑 and or xor
1221
     * @param  mixed  $field     查询字段
1222
     * @param  mixed  $op        查询表达式
1223
     * @param  mixed  $condition 查询条件
1224
     * @param  array  $param     查询参数
1225
     * @param  bool   $strict    严格模式
1226
     * @return $this
1227
     */
1228
    protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
1229
    {
1230
        $logic = strtoupper($logic);
1231
1232
        if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) {
1233
            $field = $this->options['via'] . '.' . $field;
1234
        }
1235
1236
        if ($field instanceof Raw) {
1237
            return $this->whereRaw($field, is_array($op) ? $op : []);
1238
        } elseif ($strict) {
1239
            // 使用严格模式查询
1240
            if ('=' == $op) {
1241
                $where = $this->whereEq($field, $condition);
1242
            } else {
1243
                $where = [$field, $op, $condition, $logic];
1244
            }
1245
        } elseif (is_array($field)) {
1246
            // 解析数组批量查询
1247
            return $this->parseArrayWhereItems($field, $logic);
1248
        } elseif ($field instanceof Closure) {
1249
            $where = $field;
1250
        } elseif (is_string($field)) {
1251
            if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
1252
                return $this->whereRaw($field, is_array($op) ? $op : []);
1253
            } elseif (is_string($op) && strtolower($op) == 'exp') {
1254
                $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
1255
                return $this->whereExp($field, $condition, $bind, $logic);
1256
            }
1257
1258
            $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
1259
        }
1260
1261
        if (!empty($where)) {
1262
            $this->options['where'][$logic][] = $where;
1263
        }
1264
1265
        return $this;
1266
    }
1267
1268
    /**
1269
     * 分析查询表达式
1270
     * @access protected
1271
     * @param  string   $logic     查询逻辑 and or xor
1272
     * @param  mixed    $field     查询字段
1273
     * @param  mixed    $op        查询表达式
1274
     * @param  mixed    $condition 查询条件
1275
     * @param  array    $param     查询参数
1276
     * @return array
1277
     */
1278
    protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
1279
    {
1280
        if (is_array($op)) {
1281
            // 同一字段多条件查询
1282
            array_unshift($param, $field);
1283
            $where = $param;
1284
        } elseif ($field && is_null($condition)) {
1285
            if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
1286
                // null查询
1287
                $where = [$field, $op, ''];
1288
            } elseif ('=' == $op || is_null($op)) {
1289
                $where = [$field, 'NULL', ''];
1290
            } elseif ('<>' == $op) {
1291
                $where = [$field, 'NOTNULL', ''];
1292
            } else {
1293
                // 字段相等查询
1294
                $where = $this->whereEq($field, $op);
1295
            }
1296
        } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
1297
            $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
1298
        } else {
1299
            $where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
1300
        }
1301
1302
        return $where;
1303
    }
1304
1305
    /**
1306
     * 相等查询的主键处理
1307
     * @access protected
1308
     * @param  string $field  字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1309
     * @param  mixed  $value  字段值
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1310
     * @return array
1311
     */
1312
    protected function whereEq(string $field, $value): array
1313
    {
1314
        $where = [$field, '=', $value];
1315
        if ($this->getPk() == $field) {
1316
            $this->options['key'] = $value;
1317
        }
1318
1319
        return $where;
1320
    }
1321
1322
    /**
1323
     * 数组批量查询
1324
     * @access protected
1325
     * @param  array  $field 批量查询
1326
     * @param  string $logic 查询逻辑 and or xor
1327
     * @return $this
1328
     */
1329
    protected function parseArrayWhereItems(array $field, string $logic)
1330
    {
1331
        if (key($field) !== 0) {
1332
            $where = [];
1333
            foreach ($field as $key => $val) {
1334
                if ($val instanceof Raw) {
1335
                    $where[] = [$key, 'exp', $val];
1336
                } else {
1337
                    $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
1338
                }
1339
            }
1340
        } else {
1341
            // 数组批量查询
1342
            $where = $field;
1343
        }
1344
1345
        if (!empty($where)) {
1346
            $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
1347
        }
1348
1349
        return $this;
1350
    }
1351
1352
    /**
1353
     * 去除某个查询条件
1354
     * @access public
1355
     * @param  string $field 查询字段
1356
     * @param  string $logic 查询逻辑 and or xor
1357
     * @return $this
1358
     */
1359
    public function removeWhereField(string $field, string $logic = 'AND')
1360
    {
1361
        $logic = strtoupper($logic);
1362
1363
        if (isset($this->options['where'][$logic])) {
1364
            foreach ($this->options['where'][$logic] as $key => $val) {
1365
                if (is_array($val) && $val[0] == $field) {
1366
                    unset($this->options['where'][$logic][$key]);
1367
                }
1368
            }
1369
        }
1370
1371
        return $this;
1372
    }
1373
1374
    /**
1375
     * 去除查询参数
1376
     * @access public
1377
     * @param  string $option 参数名 留空去除所有参数
1378
     * @return $this
1379
     */
1380
    public function removeOption(string $option = '')
1381
    {
1382
        if ('' === $option) {
1383
            $this->options = [];
1384
            $this->bind    = [];
1385
        } elseif (isset($this->options[$option])) {
1386
            unset($this->options[$option]);
1387
        }
1388
1389
        return $this;
1390
    }
1391
1392
    /**
1393
     * 条件查询
1394
     * @access public
1395
     * @param  mixed         $condition  满足条件(支持闭包)
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1396
     * @param  Closure|array $query      满足条件后执行的查询表达式(闭包或数组)
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 6 found
Loading history...
1397
     * @param  Closure|array $otherwise  不满足条件后执行
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1398
     * @return $this
1399
     */
1400
    public function when($condition, $query, $otherwise = null)
1401
    {
1402
        if ($condition instanceof Closure) {
1403
            $condition = $condition($this);
1404
        }
1405
1406
        if ($condition) {
1407
            if ($query instanceof Closure) {
1408
                $query($this, $condition);
1409
            } elseif (is_array($query)) {
0 ignored issues
show
introduced by
The condition is_array($query) is always true.
Loading history...
1410
                $this->where($query);
1411
            }
1412
        } elseif ($otherwise) {
1413
            if ($otherwise instanceof Closure) {
1414
                $otherwise($this, $condition);
1415
            } elseif (is_array($otherwise)) {
0 ignored issues
show
introduced by
The condition is_array($otherwise) is always true.
Loading history...
1416
                $this->where($otherwise);
1417
            }
1418
        }
1419
1420
        return $this;
1421
    }
1422
1423
    /**
1424
     * 指定查询数量
1425
     * @access public
1426
     * @param  int $offset 起始位置
1427
     * @param  int $length 查询数量
1428
     * @return $this
1429
     */
1430
    public function limit(int $offset, int $length = null)
1431
    {
1432
        $this->options['limit'] = $offset . ($length ? ',' . $length : '');
1433
1434
        return $this;
1435
    }
1436
1437
    /**
1438
     * 指定分页
1439
     * @access public
1440
     * @param  int $page     页数
1441
     * @param  int $listRows 每页数量
1442
     * @return $this
1443
     */
1444
    public function page(int $page, int $listRows = null)
1445
    {
1446
        $this->options['page'] = [$page, $listRows];
1447
1448
        return $this;
1449
    }
1450
1451
    /**
1452
     * 分页查询
1453
     * @access public
1454
     * @param  int|array $listRows 每页数量 数组表示配置参数
1455
     * @param  int|bool  $simple   是否简洁模式或者总记录数
1456
     * @param  array     $config   配置参数
1457
     * @return \think\Paginator
1458
     * @throws DbException
1459
     */
1460
    public function paginate($listRows = null, $simple = false, $config = [])
1461
    {
1462
        if (is_int($simple)) {
1463
            $total  = $simple;
1464
            $simple = false;
1465
        }
1466
1467
        $paginate = array_merge($this->paginateConfig, Container::pull('config')->get('paginate'));
1468
1469
        if (is_array($listRows)) {
1470
            $config   = array_merge($paginate, $listRows);
1471
            $listRows = intval($config['list_rows']);
1472
        } else {
1473
            $config   = array_merge($paginate, $config);
1474
            $listRows = intval($listRows ?: $config['list_rows']);
1475
        }
1476
1477
        $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
1478
        $page  = isset($config['page']) ? (int) $config['page'] : call_user_func([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
1479
            $class,
1480
            'getCurrentPage',
1481
        ], $config['var_page']);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
1482
1483
        $page = $page < 1 ? 1 : $page;
1484
1485
        $config['path'] = $config['path'] ?? call_user_func([$class, 'getCurrentPath']);
1486
1487
        if (!isset($total) && !$simple) {
1488
            $options = $this->getOptions();
1489
1490
            unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
1491
1492
            $bind    = $this->bind;
1493
            $total   = $this->count();
1494
            $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
1495
        } elseif ($simple) {
1496
            $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
1497
            $total   = null;
1498
        } else {
1499
            $results = $this->page($page, $listRows)->select();
1500
        }
1501
1502
        $this->removeOption('limit');
1503
        $this->removeOption('page');
1504
1505
        return $class::make($results, $listRows, $page, $total, $simple, $config);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $total does not seem to be defined for all execution paths leading up to this point.
Loading history...
1506
    }
1507
1508
    /**
1509
     * 表达式方式指定当前操作的数据表
1510
     * @access public
1511
     * @param  mixed $table 表名
1512
     * @return $this
1513
     */
1514
    public function tableRaw(string $table)
1515
    {
1516
        $this->options['table'] = new Raw($table);
1517
1518
        return $this;
1519
    }
1520
1521
    /**
1522
     * 指定当前操作的数据表
1523
     * @access public
1524
     * @param  mixed $table 表名
1525
     * @return $this
1526
     */
1527
    public function table($table)
1528
    {
1529
        if (is_string($table)) {
1530
            if (strpos($table, ')')) {
1531
                // 子查询
1532
            } else {
1533
                $tables = explode(',', $table);
1534
                $table  = [];
1535
1536
                foreach ($tables as $item) {
1537
                    $item = trim($item);
1538
                    if (strpos($item, ' ')) {
1539
                        list($item, $alias) = explode(' ', $item);
1540
                        $this->alias([$item => $alias]);
1541
                        $table[$item] = $alias;
1542
                    } else {
1543
                        $table[] = $item;
1544
                    }
1545
                }
1546
            }
1547
        } elseif (is_array($table)) {
1548
            $tables = $table;
1549
            $table  = [];
1550
1551
            foreach ($tables as $key => $val) {
1552
                if (is_numeric($key)) {
1553
                    $table[] = $val;
1554
                } else {
1555
                    $this->alias([$key => $val]);
1556
                    $table[$key] = $val;
1557
                }
1558
            }
1559
        }
1560
1561
        $this->options['table'] = $table;
1562
1563
        return $this;
1564
    }
1565
1566
    /**
1567
     * USING支持 用于多表删除
1568
     * @access public
1569
     * @param  mixed $using USING
1570
     * @return $this
1571
     */
1572
    public function using($using)
1573
    {
1574
        $this->options['using'] = $using;
1575
        return $this;
1576
    }
1577
1578
    /**
1579
     * 存储过程调用
1580
     * @access public
1581
     * @param  bool $procedure 是否为存储过程查询
1582
     * @return $this
1583
     */
1584
    public function procedure($procedure = true)
1585
    {
1586
        $this->options['procedure'] = $procedure;
1587
        return $this;
1588
    }
1589
1590
    /**
1591
     * 是否允许返回空数据(或空模型)
1592
     * @access public
1593
     * @param  bool $allowEmpty 是否允许为空
1594
     * @return $this
1595
     */
1596
    public function allowEmpty(bool $allowEmpty = true)
1597
    {
1598
        $this->options['allow_empty'] = $allowEmpty;
1599
        return $this;
1600
    }
1601
1602
    /**
1603
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
1604
     * @access public
1605
     * @param  string|array|Raw $field 排序字段
1606
     * @param  string           $order 排序
1607
     * @return $this
1608
     */
1609
    public function order($field, string $order = '')
1610
    {
1611
        if (empty($field)) {
1612
            return $this;
1613
        } elseif ($field instanceof Raw) {
1614
            $this->options['order'][] = $field;
1615
            return $this;
1616
        }
1617
1618
        if (is_string($field)) {
1619
            if (!empty($this->options['via'])) {
1620
                $field = $this->options['via'] . '.' . $field;
1621
            }
1622
            if (strpos($field, ',')) {
1623
                $field = array_map('trim', explode(',', $field));
1624
            } else {
1625
                $field = empty($order) ? $field : [$field => $order];
1626
            }
1627
        } elseif (!empty($this->options['via'])) {
1628
            foreach ($field as $key => $val) {
1629
                if (is_numeric($key)) {
1630
                    $field[$key] = $this->options['via'] . '.' . $val;
1631
                } else {
1632
                    $field[$this->options['via'] . '.' . $key] = $val;
1633
                    unset($field[$key]);
1634
                }
1635
            }
1636
        }
1637
1638
        if (!isset($this->options['order'])) {
1639
            $this->options['order'] = [];
1640
        }
1641
1642
        if (is_array($field)) {
1643
            $this->options['order'] = array_merge($this->options['order'], $field);
1644
        } else {
1645
            $this->options['order'][] = $field;
1646
        }
1647
1648
        return $this;
1649
    }
1650
1651
    /**
1652
     * 表达式方式指定Field排序
1653
     * @access public
1654
     * @param  string $field 排序字段
1655
     * @param  array  $bind  参数绑定
1656
     * @return $this
1657
     */
1658
    public function orderRaw(string $field, array $bind = [])
1659
    {
1660
        if (!empty($bind)) {
1661
            $this->bindParams($field, $bind);
1662
        }
1663
1664
        $this->options['order'][] = new Raw($field);
1665
1666
        return $this;
1667
    }
1668
1669
    /**
1670
     * 指定Field排序 orderField('id',[1,2,3],'desc')
1671
     * @access public
1672
     * @param  string $field 排序字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1673
     * @param  array  $values 排序值
1674
     * @param  string $order 排序 desc/asc
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1675
     * @return $this
1676
     */
1677
    public function orderField(string $field, array $values, string $order = '')
1678
    {
1679
        if (!empty($values)) {
1680
            $values['sort'] = $order;
1681
1682
            $this->options['order'][$field] = $values;
1683
        }
1684
1685
        return $this;
1686
    }
1687
1688
    /**
1689
     * 随机排序
1690
     * @access public
1691
     * @return $this
1692
     */
1693
    public function orderRand()
1694
    {
1695
        $this->options['order'][] = '[rand]';
1696
        return $this;
1697
    }
1698
1699
    /**
1700
     * 查询缓存
1701
     * @access public
1702
     * @param  mixed             $key    缓存key
1703
     * @param  integer|\DateTime $expire 缓存有效期
1704
     * @param  string            $tag    缓存标签
1705
     * @return $this
1706
     */
1707
    public function cache($key = true, $expire = null, $tag = null)
1708
    {
1709
        if (false === $key) {
1710
            return $this;
1711
        }
1712
1713
        if ($key instanceof \DateTimeInterface || (is_int($key) && is_null($expire))) {
1714
            $expire = $key;
1715
            $key    = true;
1716
        }
1717
1718
        $this->options['cache'] = [$key, $expire, $tag];
1719
1720
        return $this;
1721
    }
1722
1723
    /**
1724
     * 指定group查询
1725
     * @access public
1726
     * @param  string|array $group GROUP
1727
     * @return $this
1728
     */
1729
    public function group($group)
1730
    {
1731
        $this->options['group'] = $group;
1732
        return $this;
1733
    }
1734
1735
    /**
1736
     * 指定having查询
1737
     * @access public
1738
     * @param  string $having having
1739
     * @return $this
1740
     */
1741
    public function having(string $having)
1742
    {
1743
        $this->options['having'] = $having;
1744
        return $this;
1745
    }
1746
1747
    /**
1748
     * 指定查询lock
1749
     * @access public
1750
     * @param  bool|string $lock 是否lock
1751
     * @return $this
1752
     */
1753
    public function lock($lock = false)
1754
    {
1755
        $this->options['lock'] = $lock;
1756
1757
        if ($lock) {
1758
            $this->options['master'] = true;
1759
        }
1760
1761
        return $this;
1762
    }
1763
1764
    /**
1765
     * 指定distinct查询
1766
     * @access public
1767
     * @param  bool $distinct 是否唯一
1768
     * @return $this
1769
     */
1770
    public function distinct(bool $distinct = true)
1771
    {
1772
        $this->options['distinct'] = $distinct;
1773
        return $this;
1774
    }
1775
1776
    /**
1777
     * 指定数据表别名
1778
     * @access public
1779
     * @param  array|string $alias 数据表别名
1780
     * @return $this
1781
     */
1782
    public function alias($alias)
1783
    {
1784
        if (is_array($alias)) {
1785
            $this->options['alias'] = $alias;
1786
        } else {
1787
            $table = $this->getTable();
1788
1789
            $this->options['alias'][$table] = $alias;
1790
        }
1791
1792
        return $this;
1793
    }
1794
1795
    /**
1796
     * 指定强制索引
1797
     * @access public
1798
     * @param  string $force 索引名称
1799
     * @return $this
1800
     */
1801
    public function force(string $force)
1802
    {
1803
        $this->options['force'] = $force;
1804
        return $this;
1805
    }
1806
1807
    /**
1808
     * 查询注释
1809
     * @access public
1810
     * @param  string $comment 注释
1811
     * @return $this
1812
     */
1813
    public function comment(string $comment)
1814
    {
1815
        $this->options['comment'] = $comment;
1816
        return $this;
1817
    }
1818
1819
    /**
1820
     * 获取执行的SQL语句而不进行实际的查询
1821
     * @access public
1822
     * @param  bool $fetch 是否返回sql
1823
     * @return $this|Fetch
1824
     */
1825
    public function fetchSql(bool $fetch = true)
1826
    {
1827
        $this->options['fetch_sql'] = $fetch;
1828
1829
        if ($fetch) {
1830
            return new Fetch($this);
1831
        }
1832
1833
        return $this;
1834
    }
1835
1836
    /**
1837
     * 设置是否返回数据集对象
1838
     * @access public
1839
     * @param  bool|string $collection 是否返回数据集对象
1840
     * @return $this
1841
     */
1842
    public function fetchCollection($collection = true)
1843
    {
1844
        $this->options['collection'] = $collection;
1845
        return $this;
1846
    }
1847
1848
    /**
1849
     * 设置是否返回数组
1850
     * @access public
1851
     * @param  bool $asArray 是否返回数组
1852
     * @return $this
1853
     */
1854
    public function fetchArray(bool $asArray = true)
1855
    {
1856
        $this->options['array'] = $asArray;
1857
        return $this;
1858
    }
1859
1860
    /**
1861
     * 设置从主服务器读取数据
1862
     * @access public
1863
     * @param  bool $readMaster 是否从主服务器读取
1864
     * @return $this
1865
     */
1866
    public function master(bool $readMaster = true)
1867
    {
1868
        $this->options['master'] = $readMaster;
1869
        return $this;
1870
    }
1871
1872
    /**
1873
     * 设置后续从主库读取数据
1874
     * @access public
1875
     * @param  bool $all 是否所有表有效
1876
     * @return $this
1877
     */
1878
    public function readMaster(bool $all = false)
1879
    {
1880
        $table = $all ? '*' : $this->getTable();
1881
1882
        Db::readMaster($table);
0 ignored issues
show
Unused Code introduced by
The call to think\facade\Db::readMaster() has too many arguments starting with $table. ( Ignorable by Annotation )

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

1882
        Db::/** @scrutinizer ignore-call */ 
1883
            readMaster($table);

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...
1883
1884
        return $this;
1885
    }
1886
1887
    /**
1888
     * 设置是否严格检查字段名
1889
     * @access public
1890
     * @param  bool $strict 是否严格检查字段
1891
     * @return $this
1892
     */
1893
    public function strict(bool $strict = true)
1894
    {
1895
        $this->options['strict'] = $strict;
1896
        return $this;
1897
    }
1898
1899
    /**
1900
     * 设置查询数据不存在是否抛出异常
1901
     * @access public
1902
     * @param  bool $fail 数据不存在是否抛出异常
1903
     * @return $this
1904
     */
1905
    public function failException(bool $fail = true)
1906
    {
1907
        $this->options['fail'] = $fail;
1908
        return $this;
1909
    }
1910
1911
    /**
1912
     * 设置自增序列名
1913
     * @access public
1914
     * @param  string $sequence 自增序列名
1915
     * @return $this
1916
     */
1917
    public function sequence(string $sequence = null)
1918
    {
1919
        $this->options['sequence'] = $sequence;
1920
        return $this;
1921
    }
1922
1923
    /**
1924
     * 设置是否REPLACE
1925
     * @access public
1926
     * @param  bool $replace 是否使用REPLACE写入数据
1927
     * @return $this
1928
     */
1929
    public function replace(bool $replace = true)
1930
    {
1931
        $this->options['replace'] = $replace;
1932
        return $this;
1933
    }
1934
1935
    /**
1936
     * 设置当前查询所在的分区
1937
     * @access public
1938
     * @param  string|array $partition 分区名称
1939
     * @return $this
1940
     */
1941
    public function partition($partition)
1942
    {
1943
        $this->options['partition'] = $partition;
1944
        return $this;
1945
    }
1946
1947
    /**
1948
     * 设置DUPLICATE
1949
     * @access public
1950
     * @param  array|string|Raw $duplicate DUPLICATE信息
1951
     * @return $this
1952
     */
1953
    public function duplicate($duplicate)
1954
    {
1955
        $this->options['duplicate'] = $duplicate;
1956
        return $this;
1957
    }
1958
1959
    /**
1960
     * 设置查询的额外参数
1961
     * @access public
1962
     * @param  string $extra 额外信息
1963
     * @return $this
1964
     */
1965
    public function extra(string $extra)
1966
    {
1967
        $this->options['extra'] = $extra;
1968
        return $this;
1969
    }
1970
1971
    /**
1972
     * 设置需要隐藏的输出属性
1973
     * @access public
1974
     * @param  array $hidden 需要隐藏的字段名
1975
     * @return $this
1976
     */
1977
    public function hidden(array $hidden)
1978
    {
1979
        $this->options['hidden'] = $hidden;
1980
        return $this;
1981
    }
1982
1983
    /**
1984
     * 设置需要输出的属性
1985
     * @access public
1986
     * @param  array $visible 需要输出的属性
1987
     * @return $this
1988
     */
1989
    public function visible(array $visible)
1990
    {
1991
        $this->options['visible'] = $visible;
1992
        return $this;
1993
    }
1994
1995
    /**
1996
     * 设置需要追加输出的属性
1997
     * @access public
1998
     * @param  array $append 需要追加的属性
1999
     * @return $this
2000
     */
2001
    public function append(array $append)
2002
    {
2003
        $this->options['append'] = $append;
2004
        return $this;
2005
    }
2006
2007
    /**
2008
     * 设置JSON字段信息
2009
     * @access public
2010
     * @param  array $json JSON字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
2011
     * @param  bool  $assoc 是否取出数组
2012
     * @return $this
2013
     */
2014
    public function json(array $json = [], bool $assoc = false)
2015
    {
2016
        $this->options['json']       = $json;
2017
        $this->options['json_assoc'] = $assoc;
2018
        return $this;
2019
    }
2020
2021
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
2022
     * 添加查询范围
2023
     * @access public
2024
     * @param  array|string|Closure $scope 查询范围定义
2025
     * @param  array                $args  参数
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter $args does not match actual variable name ...$args
Loading history...
2026
     * @return $this
2027
     */
2028
    public function scope($scope, ...$args)
2029
    {
2030
        // 查询范围的第一个参数始终是当前查询对象
2031
        array_unshift($args, $this);
2032
2033
        if ($scope instanceof Closure) {
2034
            call_user_func_array($scope, $args);
2035
            return $this;
2036
        }
2037
2038
        if (is_string($scope)) {
2039
            $scope = explode(',', $scope);
2040
        }
2041
2042
        if ($this->model) {
2043
            // 检查模型类的查询范围方法
2044
            foreach ($scope as $name) {
2045
                $method = 'scope' . trim($name);
2046
2047
                if (method_exists($this->model, $method)) {
2048
                    call_user_func_array([$this->model, $method], $args);
2049
                }
2050
            }
2051
        }
2052
2053
        return $this;
2054
    }
2055
2056
    /**
2057
     * 指定数据表主键
2058
     * @access public
2059
     * @param  string $pk 主键
2060
     * @return $this
2061
     */
2062
    public function pk(string $pk)
2063
    {
2064
        $this->pk = $pk;
2065
        return $this;
2066
    }
2067
2068
    /**
2069
     * 添加日期或者时间查询规则
2070
     * @access public
2071
     * @param  string       $name 时间表达式
2072
     * @param  string|array $rule 时间范围
2073
     * @return $this
2074
     */
2075
    public function timeRule(string $name, $rule)
2076
    {
2077
        $this->timeRule[$name] = $rule;
2078
        return $this;
2079
    }
2080
2081
    /**
2082
     * 查询日期或者时间
2083
     * @access public
2084
     * @param  string       $field 日期字段名
2085
     * @param  string       $op    比较运算符或者表达式
2086
     * @param  string|array $range 比较范围
2087
     * @param  string       $logic AND OR
2088
     * @return $this
2089
     */
2090
    public function whereTime(string $field, string $op, $range = null, string $logic = 'AND')
2091
    {
2092
        if (is_null($range) && isset($this->timeRule[$op])) {
2093
            $range = $this->timeRule[$op];
2094
            $op    = 'between';
2095
        }
2096
2097
        return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
2098
    }
2099
2100
    /**
2101
     * 查询某个时间间隔数据
2102
     * @access protected
2103
     * @param  string $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2104
     * @param  string $start 开始时间
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2105
     * @param  string $interval 时间间隔单位
2106
     * @param  string $logic AND OR
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2107
     * @return $this
2108
     */
2109
    protected function whereTimeInterval(string $field, string $start, string $interval = 'day', string $logic = 'AND')
2110
    {
2111
        $startTime = strtotime($start);
2112
        $endTime   = strtotime('+1 ' . $interval, $startTime);
2113
2114
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2115
    }
2116
2117
    /**
2118
     * 查询月数据 whereMonth('time_field', '2018-1')
2119
     * @access public
2120
     * @param  string $field 日期字段名
2121
     * @param  string $month 月份信息
2122
     * @param  string $logic AND OR
2123
     * @return $this
2124
     */
2125
    public function whereMonth(string $field, string $month = 'this month', string $logic = 'AND')
2126
    {
2127
        if (in_array($month, ['this month', 'last month'])) {
2128
            $month = date('Y-m', strtotime($month));
2129
        }
2130
2131
        return $this->whereTimeInterval($field, $month, 'month', $logic);
2132
    }
2133
2134
    /**
2135
     * 查询年数据 whereYear('time_field', '2018')
2136
     * @access public
2137
     * @param  string $field 日期字段名
2138
     * @param  string $year  年份信息
2139
     * @param  string $logic AND OR
2140
     * @return $this
2141
     */
2142
    public function whereYear(string $field, string $year = 'this year', string $logic = 'AND')
2143
    {
2144
        if (in_array($year, ['this year', 'last year'])) {
2145
            $year = date('Y', strtotime($year));
2146
        }
2147
2148
        return $this->whereTimeInterval($field, $year . '-1-1', 'year', $logic);
2149
    }
2150
2151
    /**
2152
     * 查询日数据 whereDay('time_field', '2018-1-1')
2153
     * @access public
2154
     * @param  string $field 日期字段名
2155
     * @param  string $day   日期信息
2156
     * @param  string $logic AND OR
2157
     * @return $this
2158
     */
2159
    public function whereDay(string $field, string $day = 'today', string $logic = 'AND')
2160
    {
2161
        if (in_array($day, ['today', 'yesterday'])) {
2162
            $day = date('Y-m-d', strtotime($day));
2163
        }
2164
2165
        return $this->whereTimeInterval($field, $day, 'day', $logic);
2166
    }
2167
2168
    /**
2169
     * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15')
2170
     * @access public
2171
     * @param  string     $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2172
     * @param  string|int $startTime    开始时间
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2173
     * @param  string|int $endTime 结束时间
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2174
     * @param  string     $logic AND OR
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2175
     * @return $this
2176
     */
2177
    public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND')
2178
    {
2179
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2180
    }
2181
2182
    /**
2183
     * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15')
2184
     * @access public
2185
     * @param  string     $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2186
     * @param  string|int $startTime    开始时间
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2187
     * @param  string|int $endTime 结束时间
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2188
     * @return $this
2189
     */
2190
    public function whereNotBetweenTime(string $field, $startTime, $endTime)
2191
    {
2192
        return $this->whereTime($field, '<', $startTime)
2193
            ->whereTime($field, '>', $endTime);
2194
    }
2195
2196
    /**
2197
     * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time')
2198
     * @access public
2199
     * @param  string $startField 开始时间字段
2200
     * @param  string $endField   结束时间字段
2201
     * @return $this
2202
     */
2203
    public function whereBetweenTimeField(string $startField, string $endField)
2204
    {
2205
        return $this->whereTime($startField, '<=', time())
2206
            ->whereTime($endField, '>=', time());
2207
    }
2208
2209
    /**
2210
     * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time')
2211
     * @access public
2212
     * @param  string $startField 开始时间字段
2213
     * @param  string $endField   结束时间字段
2214
     * @return $this
2215
     */
2216
    public function whereNotBetweenTimeField(string $startField, string $endField)
2217
    {
2218
        return $this->whereTime($startField, '>', time())
2219
            ->whereTime($endField, '<', time(), 'OR');
2220
    }
2221
2222
    /**
2223
     * 获取当前数据表的主键
2224
     * @access public
2225
     * @return string|array
2226
     */
2227
    public function getPk()
2228
    {
2229
        if (!empty($this->pk)) {
2230
            $pk = $this->pk;
2231
        } else {
2232
            $this->pk = $pk = $this->connection->getPk($this->getTable());
2233
        }
2234
2235
        return $pk;
2236
    }
2237
2238
    /**
2239
     * 批量参数绑定
2240
     * @access public
2241
     * @param  array $value 绑定变量值
2242
     * @return $this
2243
     */
2244
    public function bind(array $value)
2245
    {
2246
        $this->bind = array_merge($this->bind, $value);
2247
        return $this;
2248
    }
2249
2250
    /**
2251
     * 单个参数绑定
2252
     * @access public
2253
     * @param  mixed   $value 绑定变量值
2254
     * @param  integer $type  绑定类型
2255
     * @param  string  $name  绑定标识
2256
     * @return string
2257
     */
2258
    public function bindValue($value, int $type = null, string $name = null)
2259
    {
2260
        $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_';
2261
2262
        $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR];
2263
        return $name;
2264
    }
2265
2266
    /**
2267
     * 检测参数是否已经绑定
2268
     * @access public
2269
     * @param  string $key 参数名
2270
     * @return bool
2271
     */
2272
    public function isBind($key)
2273
    {
2274
        return isset($this->bind[$key]);
2275
    }
2276
2277
    /**
2278
     * 参数绑定
2279
     * @access public
2280
     * @param  string $sql  绑定的sql表达式
2281
     * @param  array  $bind 参数绑定
2282
     * @return void
2283
     */
2284
    protected function bindParams(string &$sql, array $bind = []): void
2285
    {
2286
        foreach ($bind as $key => $value) {
2287
            if (is_array($value)) {
2288
                $name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
2289
            } else {
2290
                $name = $this->bindValue($value);
2291
            }
2292
2293
            if (is_numeric($key)) {
2294
                $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
2295
            } else {
2296
                $sql = str_replace(':' . $key, ':' . $name, $sql);
2297
            }
2298
        }
2299
    }
2300
2301
    /**
2302
     * 查询参数批量赋值
2303
     * @access protected
2304
     * @param  array $options 表达式参数
2305
     * @return $this
2306
     */
2307
    protected function options(array $options)
2308
    {
2309
        $this->options = $options;
2310
        return $this;
2311
    }
2312
2313
    /**
2314
     * 获取当前的查询参数
2315
     * @access public
2316
     * @param  string $name 参数名
2317
     * @return mixed
2318
     */
2319
    public function getOptions(string $name = '')
2320
    {
2321
        if ('' === $name) {
2322
            return $this->options;
2323
        }
2324
2325
        return $this->options[$name] ?? null;
2326
    }
2327
2328
    /**
2329
     * 设置当前的查询参数
2330
     * @access public
2331
     * @param  string $option 参数名
2332
     * @param  mixed  $value  参数值
2333
     * @return $this
2334
     */
2335
    public function setOption(string $option, $value)
2336
    {
2337
        $this->options[$option] = $value;
2338
        return $this;
2339
    }
2340
2341
    /**
2342
     * 设置关联查询
2343
     * @access public
2344
     * @param  array $relation 关联名称
2345
     * @return $this
2346
     */
2347
    public function relation(array $relation)
2348
    {
2349
        if (!empty($relation)) {
2350
            $this->options['relation'] = $relation;
2351
        }
2352
2353
        return $this;
2354
    }
2355
2356
    /**
2357
     * 设置关联查询JOIN预查询
2358
     * @access public
2359
     * @param  array $with 关联方法名称(数组)
2360
     * @return $this
2361
     */
2362
    public function with(array $with)
2363
    {
2364
        if (!empty($with)) {
2365
            $this->options['with'] = $with;
2366
        }
2367
2368
        return $this;
2369
    }
2370
2371
    /**
2372
     * 关联预载入 JOIN方式
2373
     * @access protected
2374
     * @param  array  $with 关联方法名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2375
     * @param  string $joinType JOIN方式
2376
     * @return $this
2377
     */
2378
    public function withJoin(array $with, string $joinType = '')
2379
    {
2380
        if (empty($with)) {
2381
            return $this;
2382
        }
2383
2384
        $first = true;
2385
2386
        /** @var Model $class */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
2387
        $class = $this->model;
2388
        foreach ($with as $key => $relation) {
2389
            $closure = null;
2390
            $field   = true;
2391
2392
            if ($relation instanceof Closure) {
2393
                // 支持闭包查询过滤关联条件
2394
                $closure  = $relation;
2395
                $relation = $key;
2396
            } elseif (is_array($relation)) {
2397
                $field    = $relation;
2398
                $relation = $key;
2399
            } elseif (is_string($relation) && strpos($relation, '.')) {
2400
                $relation = strstr($relation, '.', true);
2401
            }
2402
2403
            /** @var Relation $model */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
2404
            $relation = App::parseName($relation, 1, false);
2405
            $model    = $class->$relation();
2406
2407
            if ($model instanceof OneToOne) {
2408
                $model->eagerly($this, $relation, $field, $joinType, $closure, $first);
2409
                $first = false;
2410
            } else {
2411
                // 不支持其它关联
2412
                unset($with[$key]);
2413
            }
2414
        }
2415
2416
        $this->via();
2417
2418
        $this->options['with_join'] = $with;
2419
2420
        return $this;
2421
    }
2422
2423
    /**
2424
     * 设置数据字段获取器
2425
     * @access public
2426
     * @param  string   $name     字段名
2427
     * @param  callable $callback 闭包获取器
2428
     * @return $this
2429
     */
2430
    public function withAttr(string $name, callable $callback)
2431
    {
2432
        $this->options['with_attr'][$name] = $callback;
2433
2434
        return $this;
2435
    }
2436
2437
    /**
2438
     * 设置数据字段获取器
2439
     * @access public
2440
     * @param  array $attrs 字段获取器
2441
     * @return $this
2442
     */
2443
    public function withAttrs(array $attrs)
2444
    {
2445
        $this->options['with_attr'] = $attrs;
2446
2447
        return $this;
2448
    }
2449
2450
    /**
2451
     * 使用搜索器条件搜索字段
2452
     * @access public
2453
     * @param  array  $fields 搜索字段
2454
     * @param  array  $data   搜索数据
2455
     * @param  string $prefix 字段前缀标识
2456
     * @return $this
2457
     */
2458
    public function withSearch(array $fields, array $data = [], string $prefix = '')
2459
    {
2460
        foreach ($fields as $key => $field) {
2461
            if ($field instanceof Closure) {
2462
                $field($this, $data[$key] ?? null, $data, $prefix);
2463
            } elseif ($this->model) {
2464
                // 检测搜索器
2465
                $fieldName = is_numeric($key) ? $field : $key;
2466
                $method    = 'search' . App::parseName($fieldName, 1) . 'Attr';
2467
2468
                if (method_exists($this->model, $method)) {
2469
                    $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
2470
                }
2471
            }
2472
        }
2473
2474
        return $this;
2475
    }
2476
2477
    /**
2478
     * 关联统计
2479
     * @access protected
2480
     * @param  array|string $relations 关联方法名
2481
     * @param  string       $aggregate 聚合查询方法
2482
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2483
     * @param  bool         $subQuery 是否使用子查询
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
2484
     * @return $this
2485
     */
2486
    protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
2487
    {
2488
        if (is_string($relations)) {
2489
            $relations = explode(',', $relations);
2490
        }
2491
2492
        if (!$subQuery) {
2493
            $this->options['with_count'][] = [$relations, $aggregate, $field];
2494
        } else {
2495
            if (!isset($this->options['field'])) {
2496
                $this->field('*');
2497
            }
2498
2499
            foreach ($relations as $key => $relation) {
2500
                $closure = $aggregateField = null;
2501
2502
                if ($relation instanceof Closure) {
2503
                    $closure  = $relation;
2504
                    $relation = $key;
2505
                } elseif (!is_int($key)) {
2506
                    $aggregateField = $relation;
2507
                    $relation       = $key;
2508
                }
2509
2510
                $relation = App::parseName($relation, 1, false);
2511
2512
                $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField) . ')';
2513
2514
                if (empty($aggregateField)) {
2515
                    $aggregateField = App::parseName($relation) . '_' . $aggregate;
2516
                }
2517
2518
                $this->field([$count => $aggregateField]);
2519
            }
2520
        }
2521
2522
        return $this;
2523
    }
2524
2525
    /**
2526
     * 关联统计
2527
     * @access public
2528
     * @param  string|array $relation 关联方法名
2529
     * @param  bool         $subQuery 是否使用子查询
2530
     * @return $this
2531
     */
2532
    public function withCount($relation, bool $subQuery = true)
2533
    {
2534
        return $this->withAggregate($relation, 'count', '*', $subQuery);
2535
    }
2536
2537
    /**
2538
     * 关联统计Sum
2539
     * @access public
2540
     * @param  string|array $relation 关联方法名
2541
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2542
     * @param  bool         $subQuery 是否使用子查询
2543
     * @return $this
2544
     */
2545
    public function withSum($relation, string $field, bool $subQuery = true)
2546
    {
2547
        return $this->withAggregate($relation, 'sum', $field, $subQuery);
2548
    }
2549
2550
    /**
2551
     * 关联统计Max
2552
     * @access public
2553
     * @param  string|array $relation 关联方法名
2554
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2555
     * @param  bool         $subQuery 是否使用子查询
2556
     * @return $this
2557
     */
2558
    public function withMax($relation, string $field, bool $subQuery = true)
2559
    {
2560
        return $this->withAggregate($relation, 'max', $field, $subQuery);
2561
    }
2562
2563
    /**
2564
     * 关联统计Min
2565
     * @access public
2566
     * @param  string|array $relation 关联方法名
2567
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2568
     * @param  bool         $subQuery 是否使用子查询
2569
     * @return $this
2570
     */
2571
    public function withMin($relation, string $field, bool $subQuery = true)
2572
    {
2573
        return $this->withAggregate($relation, 'min', $field, $subQuery);
2574
    }
2575
2576
    /**
2577
     * 关联统计Avg
2578
     * @access public
2579
     * @param  string|array $relation 关联方法名
2580
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2581
     * @param  bool         $subQuery 是否使用子查询
2582
     * @return $this
2583
     */
2584
    public function withAvg($relation, string $field, bool $subQuery = true)
2585
    {
2586
        return $this->withAggregate($relation, 'avg', $field, $subQuery);
2587
    }
2588
2589
    /**
2590
     * 关联预加载中 获取关联指定字段值
2591
     * example:
2592
     * Model::with(['relation' => function($query){
2593
     *     $query->withField("id,name");
2594
     * }])
2595
     *
2596
     * @access public
2597
     * @param  string|array $field 指定获取的字段
2598
     * @return $this
2599
     */
2600
    public function withField($field)
2601
    {
2602
        $this->options['with_field'] = $field;
2603
2604
        return $this;
2605
    }
2606
2607
    /**
2608
     * 设置当前字段添加的表别名
2609
     * @access public
2610
     * @param  string $via 临时表别名
2611
     * @return $this
2612
     */
2613
    public function via(string $via = '')
2614
    {
2615
        $this->options['via'] = $via;
2616
2617
        return $this;
2618
    }
2619
2620
    /**
2621
     * 保存记录 自动判断insert或者update
2622
     * @access public
2623
     * @param  array $data        数据
2624
     * @param  bool  $forceInsert 是否强制insert
2625
     * @return integer
2626
     */
2627
    public function save(array $data = [], bool $forceInsert = false)
2628
    {
2629
        if ($forceInsert) {
2630
            return $this->insert($data);
2631
        }
2632
2633
        if (!empty($data)) {
2634
            $this->options['data'] = $data;
2635
        }
2636
2637
        if (!empty($this->options['where'])) {
2638
            $isUpdate = true;
2639
        } else {
2640
            $isUpdate = $this->parseUpdateData($this->options['data']);
2641
        }
2642
2643
        return $isUpdate ? $this->update() : $this->insert();
2644
    }
2645
2646
    /**
2647
     * 插入记录
2648
     * @access public
2649
     * @param  array   $data         数据
2650
     * @param  boolean $getLastInsID 返回自增主键
2651
     * @return integer
2652
     */
2653
    public function insert(array $data = [], bool $getLastInsID = false)
2654
    {
2655
        if (!empty($data)) {
2656
            $this->options['data'] = $data;
2657
        }
2658
2659
        return $this->connection->insert($this, $getLastInsID);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection...t($this, $getLastInsID) also could return the type string which is incompatible with the documented return type integer.
Loading history...
2660
    }
2661
2662
    /**
2663
     * 插入记录并获取自增ID
2664
     * @access public
2665
     * @param  array   $data     数据
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
2666
     * @return integer|string
2667
     */
2668
    public function insertGetId(array $data)
2669
    {
2670
        return $this->insert($data, true);
2671
    }
2672
2673
    /**
2674
     * 批量插入记录
2675
     * @access public
2676
     * @param  array   $dataSet 数据集
2677
     * @param  integer $limit   每次写入数据限制
2678
     * @return integer
2679
     */
2680
    public function insertAll(array $dataSet = [], int $limit = 0): int
2681
    {
2682
        if (empty($dataSet)) {
2683
            $dataSet = $this->options['data'] ?? [];
2684
        }
2685
2686
        if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
2687
            $limit = (int) $this->options['limit'];
2688
        }
2689
2690
        return $this->connection->insertAll($this, $dataSet, $limit);
2691
    }
2692
2693
    /**
2694
     * 通过Select方式插入记录
2695
     * @access public
2696
     * @param  array  $fields 要插入的数据表字段名
2697
     * @param  string $table  要插入的数据表名
2698
     * @return integer
2699
     * @throws PDOException
2700
     */
2701
    public function selectInsert(array $fields, string $table): int
2702
    {
2703
        return $this->connection->selectInsert($this, $fields, $table);
2704
    }
2705
2706
    /**
2707
     * 更新记录
2708
     * @access public
2709
     * @param  mixed $data 数据
2710
     * @return integer
2711
     * @throws Exception
2712
     * @throws PDOException
2713
     */
2714
    public function update(array $data = []): int
2715
    {
2716
        if (empty($data)) {
2717
            $data = $this->options['data'] ?? [];
2718
        }
2719
2720
        if (empty($this->options['where'])) {
2721
            $this->parseUpdateData($data);
2722
        }
2723
2724
        if (empty($this->options['where']) && $this->model) {
2725
            $this->where($this->model->getWhere());
2726
        }
2727
2728
        if (empty($this->options['where'])) {
2729
            // 如果没有任何更新条件则不执行
2730
            throw new Exception('miss update condition');
2731
        }
2732
2733
        $this->options['data'] = $data;
2734
2735
        return $this->connection->update($this);
2736
    }
2737
2738
    /**
2739
     * 删除记录
2740
     * @access public
2741
     * @param  mixed $data 表达式 true 表示强制删除
2742
     * @return int
2743
     * @throws Exception
2744
     * @throws PDOException
2745
     */
2746
    public function delete($data = null): int
2747
    {
2748
        if (!is_null($data) && true !== $data) {
2749
            // AR模式分析主键条件
2750
            $this->parsePkWhere($data);
2751
        }
2752
2753
        if (empty($this->options['where']) && $this->model) {
2754
            $this->where($this->model->getWhere());
2755
        }
2756
2757
        if (true !== $data && empty($this->options['where'])) {
2758
            // 如果条件为空 不进行删除操作 除非设置 1=1
2759
            throw new Exception('delete without condition');
2760
        }
2761
2762
        if (!empty($this->options['soft_delete'])) {
2763
            // 软删除
2764
            list($field, $condition) = $this->options['soft_delete'];
2765
            if ($condition) {
2766
                unset($this->options['soft_delete']);
2767
                $this->options['data'] = [$field => $condition];
2768
2769
                return $this->connection->update($this);
2770
            }
2771
        }
2772
2773
        $this->options['data'] = $data;
2774
2775
        return $this->connection->delete($this);
2776
    }
2777
2778
    /**
2779
     * 执行查询但只返回PDOStatement对象
2780
     * @access public
2781
     * @return PDOStatement
2782
     */
2783
    public function getPdo(): PDOStatement
2784
    {
2785
        return $this->connection->pdo($this);
2786
    }
2787
2788
    /**
2789
     * 使用游标查找记录
2790
     * @access public
2791
     * @param  mixed $data 数据
2792
     * @return \Generator
2793
     */
2794
    public function cursor($data = null)
2795
    {
2796
        if (!is_null($data)) {
2797
            // 主键条件分析
2798
            $this->parsePkWhere($data);
2799
        }
2800
2801
        $this->options['data'] = $data;
2802
2803
        $connection = clone $this->connection;
2804
2805
        return $connection->cursor($this);
2806
    }
2807
2808
    /**
2809
     * 查找记录
2810
     * @access public
2811
     * @param  mixed $data 数据
2812
     * @return Collection|array|ModelCollection
2813
     * @throws DbException
2814
     * @throws ModelNotFoundException
2815
     * @throws DataNotFoundException
2816
     */
2817
    public function select($data = null)
2818
    {
2819
        if (!is_null($data)) {
2820
            // 主键条件分析
2821
            $this->parsePkWhere($data);
2822
        }
2823
2824
        $resultSet = $this->connection->select($this);
2825
2826
        // 返回结果处理
2827
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
2828
            $this->throwNotFound();
2829
        }
2830
2831
        // 数据列表读取后的处理
2832
        if (!empty($this->model) && empty($this->options['array'])) {
2833
            // 生成模型对象
2834
            $resultSet = $this->resultSetToModelCollection($resultSet);
2835
        } else {
2836
            $this->resultSet($resultSet);
2837
        }
2838
2839
        return $resultSet;
2840
    }
2841
2842
    /**
2843
     * 查询数据转换为模型数据集对象
2844
     * @access protected
2845
     * @param  array $resultSet 数据集
2846
     * @return ModelCollection
2847
     */
2848
    protected function resultSetToModelCollection(array $resultSet): ModelCollection
2849
    {
2850
        if (!empty($this->options['collection']) && is_string($this->options['collection'])) {
2851
            $collection = $this->options['collection'];
2852
        }
2853
2854
        if (empty($resultSet)) {
2855
            return $this->model->toCollection([], $collection ?? null);
2856
        }
2857
2858
        // 检查动态获取器
2859
        if (!empty($this->options['with_attr'])) {
2860
            foreach ($this->options['with_attr'] as $name => $val) {
2861
                if (strpos($name, '.')) {
2862
                    list($relation, $field) = explode('.', $name);
2863
2864
                    $withRelationAttr[$relation][$field] = $val;
2865
                    unset($this->options['with_attr'][$name]);
2866
                }
2867
            }
2868
        }
2869
2870
        $withRelationAttr = $withRelationAttr ?? [];
2871
2872
        foreach ($resultSet as $key => &$result) {
2873
            // 数据转换为模型对象
2874
            $this->resultToModel($result, $this->options, true, $withRelationAttr);
2875
        }
2876
2877
        if (!empty($this->options['with'])) {
2878
            // 预载入
2879
            $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result seems to be defined by a foreach iteration on line 2872. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2880
        }
2881
2882
        if (!empty($this->options['with_join'])) {
2883
            // 预载入
2884
            $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true);
2885
        }
2886
2887
        // 模型数据集转换
2888
        return $this->model->toCollection($resultSet, $collection ?? null);
2889
    }
2890
2891
    /**
2892
     * 处理数据集
2893
     * @access public
2894
     * @param  array $resultSet 数据集
2895
     * @return void
2896
     */
2897
    protected function resultSet(array &$resultSet): void
2898
    {
2899
        if (!empty($this->options['json'])) {
2900
            foreach ($resultSet as &$result) {
2901
                $this->jsonResult($result, $this->options['json'], true);
2902
            }
2903
        }
2904
2905
        if (!empty($this->options['with_attr'])) {
2906
            foreach ($resultSet as &$result) {
2907
                $this->getResultAttr($result, $this->options['with_attr']);
2908
            }
2909
        }
2910
2911
        if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
2912
            foreach ($resultSet as &$result) {
2913
                $this->filterResult($result);
2914
            }
2915
        }
2916
2917
        if (!empty($this->options['collection'])) {
2918
            // 返回Collection对象
2919
            $resultSet = new Collection($resultSet);
2920
        }
2921
    }
2922
2923
    /**
2924
     * 查找单条记录
2925
     * @access public
2926
     * @param  mixed $data 查询数据
2927
     * @return array|Model|null
2928
     * @throws DbException
2929
     * @throws ModelNotFoundException
2930
     * @throws DataNotFoundException
2931
     */
2932
    public function find($data = null)
2933
    {
2934
        if (!is_null($data)) {
2935
            // AR模式分析主键条件
2936
            $this->parsePkWhere($data);
2937
        }
2938
2939
        $result = $this->connection->find($this);
2940
2941
        // 数据处理
2942
        if (empty($result)) {
2943
            return $this->resultToEmpty();
2944
        }
2945
2946
        if (!empty($this->model) && empty($this->options['array'])) {
2947
            // 返回模型对象
2948
            $this->resultToModel($result, $this->options);
2949
        } else {
2950
            $this->result($result);
2951
        }
2952
2953
        return $result;
2954
    }
2955
2956
    /**
2957
     * 查找单条记录 不存在返回空数据(或者空模型)
2958
     * @access public
2959
     * @param  mixed $data 数据
2960
     * @return array|Model
2961
     */
2962
    public function findOrEmpty($data = null)
2963
    {
2964
        return $this->allowEmpty(true)->find($data);
2965
    }
2966
2967
    /**
2968
     * 处理空数据
2969
     * @access protected
2970
     * @return array|Model|null
2971
     * @throws DbException
2972
     * @throws ModelNotFoundException
2973
     * @throws DataNotFoundException
2974
     */
2975
    protected function resultToEmpty()
2976
    {
2977
        if (!empty($this->options['fail'])) {
2978
            $this->throwNotFound();
2979
        } elseif (!empty($this->options['allow_empty'])) {
2980
            return !empty($this->model) && empty($this->options['array']) ? $this->model->newInstance([], true) : [];
2981
        } elseif (!empty($this->options['array'])) {
2982
            return [];
2983
        }
2984
    }
2985
2986
    /**
2987
     * 获取模型的更新条件
2988
     * @access protected
2989
     * @param  array $options 查询参数
2990
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
2991
    protected function getModelUpdateCondition(array $options)
2992
    {
2993
        return $options['where']['AND'] ?? null;
2994
    }
2995
2996
    /**
2997
     * 处理数据
2998
     * @access protected
2999
     * @param  array $result 查询数据
3000
     * @return void
3001
     */
3002
    protected function result(array &$result): void
3003
    {
3004
        if (!empty($this->options['json'])) {
3005
            $this->jsonResult($result, $this->options['json'], true);
3006
        }
3007
3008
        if (!empty($this->options['with_attr'])) {
3009
            $this->getResultAttr($result, $this->options['with_attr']);
3010
        }
3011
3012
        $this->filterResult($result);
3013
    }
3014
3015
    /**
3016
     * 处理数据的可见和隐藏
3017
     * @access protected
3018
     * @param  array $result 查询数据
3019
     * @return void
3020
     */
3021
    protected function filterResult(&$result): void
3022
    {
3023
        if (!empty($this->options['visible'])) {
3024
            foreach ($this->options['visible'] as $key) {
3025
                $array[] = $key;
3026
            }
3027
            $result = array_intersect_key($result, array_flip($array));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $array seems to be defined by a foreach iteration on line 3024. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
3028
        } elseif (!empty($this->options['hidden'])) {
3029
            foreach ($this->options['hidden'] as $key) {
3030
                $array[] = $key;
3031
            }
3032
            $result = array_diff_key($result, array_flip($array));
3033
        }
3034
    }
3035
3036
    /**
3037
     * 使用获取器处理数据
3038
     * @access protected
3039
     * @param  array $result   查询数据
3040
     * @param  array $withAttr 字段获取器
3041
     * @return void
3042
     */
3043
    protected function getResultAttr(array &$result, array $withAttr = []): void
3044
    {
3045
        foreach ($withAttr as $name => $closure) {
3046
            $name = App::parseName($name);
3047
3048
            if (strpos($name, '.')) {
3049
                // 支持JSON字段 获取器定义
3050
                list($key, $field) = explode('.', $name);
3051
3052
                if (isset($result[$key])) {
3053
                    $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
3054
                }
3055
            } else {
3056
                $result[$name] = $closure($result[$name] ?? null, $result);
3057
            }
3058
        }
3059
    }
3060
3061
    /**
3062
     * JSON字段数据转换
3063
     * @access protected
3064
     * @param  array $result           查询数据
3065
     * @param  array $json             JSON字段
3066
     * @param  bool  $assoc            是否转换为数组
3067
     * @param  array $withRelationAttr 关联获取器
3068
     * @return void
3069
     */
3070
    protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
3071
    {
3072
        foreach ($json as $name) {
3073
            if (!isset($result[$name])) {
3074
                continue;
3075
            }
3076
3077
            $result[$name] = json_decode($result[$name], $assoc);
3078
3079
            if (!isset($withRelationAttr[$name])) {
3080
                continue;
3081
            }
3082
3083
            foreach ($withRelationAttr[$name] as $key => $closure) {
3084
                $data = get_object_vars($result[$name]);
3085
3086
                $result[$name]->$key = $closure($result[$name]->$key ?? null, $data);
3087
            }
3088
        }
3089
    }
3090
3091
    /**
3092
     * 查询数据转换为模型对象
3093
     * @access protected
3094
     * @param  array $result           查询数据
3095
     * @param  array $options          查询参数
3096
     * @param  bool  $resultSet        是否为数据集查询
3097
     * @param  array $withRelationAttr 关联字段获取器
3098
     * @return void
3099
     */
3100
    protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
3101
    {
3102
        // 动态获取器
3103
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
3104
            foreach ($options['with_attr'] as $name => $val) {
3105
                if (strpos($name, '.')) {
3106
                    list($relation, $field) = explode('.', $name);
3107
3108
                    $withRelationAttr[$relation][$field] = $val;
3109
                    unset($options['with_attr'][$name]);
3110
                }
3111
            }
3112
        }
3113
3114
        // JSON 数据处理
3115
        if (!empty($options['json'])) {
3116
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
3117
        }
3118
3119
        $result = $this->model->newInstance($result, true, $resultSet ? null : $this->getModelUpdateCondition($options));
3120
3121
        // 动态获取器
3122
        if (!empty($options['with_attr'])) {
3123
            $result->withAttribute($options['with_attr']);
3124
        }
3125
3126
        // 输出属性控制
3127
        if (!empty($options['visible'])) {
3128
            $result->visible($options['visible']);
3129
        } elseif (!empty($options['hidden'])) {
3130
            $result->hidden($options['hidden']);
3131
        }
3132
3133
        if (!empty($options['append'])) {
3134
            $result->append($options['append']);
3135
        }
3136
3137
        // 关联查询
3138
        if (!empty($options['relation'])) {
3139
            $result->relationQuery($options['relation']);
3140
        }
3141
3142
        // 预载入查询
3143
        if (!$resultSet && !empty($options['with'])) {
3144
            $result->eagerlyResult($result, $options['with'], $withRelationAttr);
3145
        }
3146
3147
        // JOIN预载入查询
3148
        if (!$resultSet && !empty($options['with_join'])) {
3149
            $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true);
3150
        }
3151
3152
        // 关联统计
3153
        if (!empty($options['with_count'])) {
3154
            foreach ($options['with_count'] as $val) {
3155
                $result->relationCount($result, $val[0], $val[1], $val[2]);
3156
            }
3157
        }
3158
    }
3159
3160
    /**
3161
     * 查询失败 抛出异常
3162
     * @access protected
3163
     * @return void
3164
     * @throws ModelNotFoundException
3165
     * @throws DataNotFoundException
3166
     */
3167
    protected function throwNotFound(): void
3168
    {
3169
        if (!empty($this->model)) {
3170
            $class = get_class($this->model);
3171
            throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options);
3172
        }
3173
3174
        $table = $this->getTable();
3175
        throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options);
3176
    }
3177
3178
    /**
3179
     * 查找多条记录 如果不存在则抛出异常
3180
     * @access public
3181
     * @param  array|string|Query|Closure $data 数据
3182
     * @return array|PDOStatement|string|Model
3183
     * @throws DbException
3184
     * @throws ModelNotFoundException
3185
     * @throws DataNotFoundException
3186
     */
3187
    public function selectOrFail($data = null)
3188
    {
3189
        return $this->failException(true)->select($data);
3190
    }
3191
3192
    /**
3193
     * 查找单条记录 如果不存在则抛出异常
3194
     * @access public
3195
     * @param  array|string|Query|Closure $data 数据
3196
     * @return array|PDOStatement|string|Model
3197
     * @throws DbException
3198
     * @throws ModelNotFoundException
3199
     * @throws DataNotFoundException
3200
     */
3201
    public function findOrFail($data = null)
3202
    {
3203
        return $this->failException(true)->find($data);
3204
    }
3205
3206
    /**
3207
     * 分批数据返回处理
3208
     * @access public
3209
     * @param  integer      $count    每次处理的数据数量
3210
     * @param  callable     $callback 处理回调方法
3211
     * @param  string|array $column   分批处理的字段名
3212
     * @param  string       $order    字段排序
3213
     * @return bool
3214
     * @throws DbException
3215
     */
3216
    public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
3217
    {
3218
        $options = $this->getOptions();
3219
        $column  = $column ?: $this->getPk();
3220
3221
        if (isset($options['order'])) {
3222
            if (Container::pull('app')->isDebug()) {
3223
                throw new DbException('chunk not support call order');
3224
            }
3225
            unset($options['order']);
3226
        }
3227
3228
        $bind = $this->bind;
3229
3230
        if (is_array($column)) {
3231
            $times = 1;
3232
            $query = $this->options($options)->page($times, $count);
3233
        } else {
3234
            $query = $this->options($options)->limit($count);
3235
3236
            if (strpos($column, '.')) {
3237
                list($alias, $key) = explode('.', $column);
3238
            } else {
3239
                $key = $column;
3240
            }
3241
        }
3242
3243
        $resultSet = $query->order($column, $order)->select();
3244
3245
        while (count($resultSet) > 0) {
3246
            if ($resultSet instanceof Collection) {
3247
                $resultSet = $resultSet->all();
3248
            }
3249
3250
            if (false === call_user_func($callback, $resultSet)) {
3251
                return false;
3252
            }
3253
3254
            if (isset($times)) {
3255
                $times++;
3256
                $query = $this->options($options)->page($times, $count);
3257
            } else {
3258
                $end    = end($resultSet);
3259
                $lastId = is_array($end) ? $end[$key] : $end->getData($key);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $key does not seem to be defined for all execution paths leading up to this point.
Loading history...
3260
3261
                $query = $this->options($options)
3262
                    ->limit($count)
3263
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
3264
            }
3265
3266
            $resultSet = $query->bind($bind)->order($column, $order)->select();
3267
        }
3268
3269
        return true;
3270
    }
3271
3272
    /**
3273
     * 获取绑定的参数 并清空
3274
     * @access public
3275
     * @param  bool $clear 是否清空绑定数据
3276
     * @return array
3277
     */
3278
    public function getBind(bool $clear = true): array
3279
    {
3280
        $bind = $this->bind;
3281
        if ($clear) {
3282
            $this->bind = [];
3283
        }
3284
3285
        return $bind;
3286
    }
3287
3288
    /**
3289
     * 创建子查询SQL
3290
     * @access public
3291
     * @param  bool $sub 是否添加括号
3292
     * @return string
3293
     * @throws DbException
3294
     */
3295
    public function buildSql(bool $sub = true): string
3296
    {
3297
        return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
3298
    }
3299
3300
    /**
3301
     * 视图查询处理
3302
     * @access protected
3303
     * @param  array $options 查询参数
3304
     * @return void
3305
     */
3306
    protected function parseView(array &$options): void
3307
    {
3308
        foreach (['AND', 'OR'] as $logic) {
3309
            if (isset($options['where'][$logic])) {
3310
                foreach ($options['where'][$logic] as $key => $val) {
3311
                    if (array_key_exists($key, $options['map'])) {
3312
                        array_shift($val);
3313
                        array_unshift($val, $options['map'][$key]);
3314
                        $options['where'][$logic][$options['map'][$key]] = $val;
3315
                        unset($options['where'][$logic][$key]);
3316
                    }
3317
                }
3318
            }
3319
        }
3320
3321
        if (isset($options['order'])) {
3322
            // 视图查询排序处理
3323
            foreach ($options['order'] as $key => $val) {
3324
                if (is_numeric($key) && is_string($val)) {
3325
                    if (strpos($val, ' ')) {
3326
                        list($field, $sort) = explode(' ', $val);
3327
                        if (array_key_exists($field, $options['map'])) {
3328
                            $options['order'][$options['map'][$field]] = $sort;
3329
                            unset($options['order'][$key]);
3330
                        }
3331
                    } elseif (array_key_exists($val, $options['map'])) {
3332
                        $options['order'][$options['map'][$val]] = 'asc';
3333
                        unset($options['order'][$key]);
3334
                    }
3335
                } elseif (array_key_exists($key, $options['map'])) {
3336
                    $options['order'][$options['map'][$key]] = $val;
3337
                    unset($options['order'][$key]);
3338
                }
3339
            }
3340
        }
3341
    }
3342
3343
    /**
3344
     * 分析数据是否存在更新条件
3345
     * @access public
3346
     * @param  array $data 数据
3347
     * @return bool
3348
     * @throws Exception
3349
     */
3350
    public function parseUpdateData(&$data): bool
3351
    {
3352
        $pk       = $this->getPk();
3353
        $isUpdate = false;
3354
        // 如果存在主键数据 则自动作为更新条件
3355
        if (is_string($pk) && isset($data[$pk])) {
3356
            $this->where($pk, '=', $data[$pk]);
3357
            $this->options['key'] = $data[$pk];
3358
            unset($data[$pk]);
3359
            $isUpdate = true;
3360
        } elseif (is_array($pk)) {
3361
            // 增加复合主键支持
3362
            foreach ($pk as $field) {
3363
                if (isset($data[$field])) {
3364
                    $this->where($field, '=', $data[$field]);
3365
                    $isUpdate = true;
3366
                } else {
3367
                    // 如果缺少复合主键数据则不执行
3368
                    throw new Exception('miss complex primary data');
3369
                }
3370
                unset($data[$field]);
3371
            }
3372
        }
3373
3374
        return $isUpdate;
3375
    }
3376
3377
    /**
3378
     * 把主键值转换为查询条件 支持复合主键
3379
     * @access public
3380
     * @param  array|string $data 主键数据
3381
     * @return void
3382
     * @throws Exception
3383
     */
3384
    public function parsePkWhere($data): void
3385
    {
3386
        $pk = $this->getPk();
3387
3388
        if (is_string($pk)) {
3389
            // 获取数据表
3390
            if (empty($this->options['table'])) {
3391
                $this->options['table'] = $this->getTable();
3392
            }
3393
3394
            $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
3395
3396
            if (!empty($this->options['alias'][$table])) {
3397
                $alias = $this->options['alias'][$table];
3398
            }
3399
3400
            $key = isset($alias) ? $alias . '.' . $pk : $pk;
3401
            // 根据主键查询
3402
            if (is_array($data)) {
3403
                $this->where($key, 'in', $data);
3404
            } else {
3405
                $this->where($key, '=', $data);
3406
                $this->options['key'] = $data;
3407
            }
3408
        }
3409
    }
3410
3411
    /**
3412
     * 分析表达式(可用于查询或者写入操作)
3413
     * @access public
3414
     * @return array
3415
     */
3416
    public function parseOptions(): array
3417
    {
3418
        $options = $this->getOptions();
3419
3420
        // 获取数据表
3421
        if (empty($options['table'])) {
3422
            $options['table'] = $this->getTable();
3423
        }
3424
3425
        if (!isset($options['where'])) {
3426
            $options['where'] = [];
3427
        } elseif (isset($options['view'])) {
3428
            // 视图查询条件处理
3429
            $this->parseView($options);
3430
        }
3431
3432
        if (!isset($options['field'])) {
3433
            $options['field'] = '*';
3434
        }
3435
3436
        foreach (['data', 'order', 'join', 'union'] as $name) {
3437
            if (!isset($options[$name])) {
3438
                $options[$name] = [];
3439
            }
3440
        }
3441
3442
        if (!isset($options['strict'])) {
3443
            $options['strict'] = $this->connection->getConfig('fields_strict');
3444
        }
3445
3446
        foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
3447
            if (!isset($options[$name])) {
3448
                $options[$name] = false;
3449
            }
3450
        }
3451
3452
        if (is_string($options['table']) && Db::isReadMaster($options['table'])) {
3453
            $options['master'] = true;
3454
        }
3455
3456
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
3457
            if (!isset($options[$name])) {
3458
                $options[$name] = '';
3459
            }
3460
        }
3461
3462
        if (isset($options['page'])) {
3463
            // 根据页数计算limit
3464
            list($page, $listRows) = $options['page'];
3465
            $page                  = $page > 0 ? $page : 1;
3466
            $listRows              = $listRows ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
3467
            $offset                = $listRows * ($page - 1);
3468
            $options['limit']      = $offset . ',' . $listRows;
3469
        }
3470
3471
        $this->options = $options;
3472
3473
        return $options;
3474
    }
3475
3476
    /**
3477
     * 注册回调方法
3478
     * @access public
3479
     * @param  string   $event    事件名
3480
     * @param  callable $callback 回调方法
3481
     * @return void
3482
     */
3483
    public static function event(string $event, callable $callback): void
3484
    {
3485
        self::$event[$event] = $callback;
3486
    }
3487
3488
    /**
3489
     * 触发事件
3490
     * @access public
3491
     * @param  string $event 事件名
3492
     * @return mixed
3493
     */
3494
    public function trigger(string $event)
3495
    {
3496
        $result = false;
3497
3498
        if (isset(self::$event[$event])) {
3499
            $result = Container::getInstance()->invoke(self::$event[$event], [$this]);
3500
        }
3501
3502
        return $result;
3503
    }
3504
3505
}
3506