Passed
Push — 5.2 ( e82ddc...48fd10 )
by liu
02:37
created

Query::allowEmpty()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
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\Model;
28
use think\model\Collection as ModelCollection;
29
use think\model\Relation;
30
use think\model\relation\OneToOne;
31
32
class Query
1 ignored issue
show
Coding Style introduced by
Missing class doc comment
Loading history...
33
{
34
    /**
35
     * 当前数据库连接对象
36
     * @var Connection
37
     */
38
    protected $connection;
39
40
    /**
41
     * 当前模型对象
42
     * @var Model
43
     */
44
    protected $model;
45
46
    /**
47
     * 当前数据表名称(不含前缀)
48
     * @var string
49
     */
50
    protected $name = '';
51
52
    /**
53
     * 当前数据表主键
54
     * @var string|array
55
     */
56
    protected $pk;
57
58
    /**
59
     * 当前数据表前缀
60
     * @var string
61
     */
62
    protected $prefix = '';
63
64
    /**
65
     * 当前查询参数
66
     * @var array
67
     */
68
    protected $options = [];
69
70
    /**
71
     * 分页查询配置
72
     * @var array
73
     */
74
    protected $paginateConfig = [
75
        'query'     => [], //url额外参数
76
        'fragment'  => '', //url锚点
77
        'type'      => 'bootstrap', //分页类名
78
        'var_page'  => 'page', //分页变量
79
        'list_rows' => 15, //每页数量
80
    ];
81
82
    /**
83
     * 当前参数绑定
84
     * @var array
85
     */
86
    protected $bind = [];
87
88
    /**
89
     * 事件回调
90
     * @var array
91
     */
92
    protected static $event = [];
93
94
    /**
95
     * 日期查询表达式
96
     * @var array
97
     */
98
    protected $timeRule = [
99
        'today'      => ['today', 'tomorrow'],
100
        'yesterday'  => ['yesterday', 'today'],
101
        'week'       => ['this week 00:00:00', 'next week 00:00:00'],
102
        'last week'  => ['last week 00:00:00', 'this week 00:00:00'],
103
        'month'      => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'],
104
        'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'],
105
        'year'       => ['this year 1/1', 'next year 1/1'],
106
        'last year'  => ['last year 1/1', 'this year 1/1'],
107
    ];
108
109
    /**
110
     * 架构函数
111
     * @access public
112
     * @param  Connection      $connection 数据库连接对象
113
     */
114
    public function __construct(Connection $connection)
115
    {
116
        $this->connection = $connection;
117
118
        $this->prefix = $this->connection->getConfig('prefix');
119
    }
120
121
    /**
122
     * 创建一个新的查询对象
123
     * @access public
124
     * @return Query
125
     */
126
    public function newQuery()
127
    {
128
        return new static($this->connection);
129
    }
130
131
    /**
132
     * 利用__call方法实现一些特殊的Model方法
133
     * @access public
134
     * @param  string $method 方法名称
135
     * @param  array  $args   调用参数
136
     * @return mixed
137
     * @throws DbException
138
     * @throws Exception
139
     */
140
    public function __call(string $method, array $args)
141
    {
142
        if (strtolower(substr($method, 0, 5)) == 'getby') {
143
            // 根据某个字段获取记录
144
            $field = App::parseName(substr($method, 5));
145
            return $this->where($field, '=', $args[0])->find();
146
        } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
147
            // 根据某个字段获取记录的某个值
148
            $name = App::parseName(substr($method, 10));
149
            return $this->where($name, '=', $args[0])->value($args[1]);
150
        } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
151
            $name = App::parseName(substr($method, 7));
152
            array_unshift($args, $name);
153
            return call_user_func_array([$this, 'whereOr'], $args);
154
        } elseif (strtolower(substr($method, 0, 5)) == 'where') {
155
            $name = App::parseName(substr($method, 5));
156
            array_unshift($args, $name);
157
            return call_user_func_array([$this, 'where'], $args);
158
        } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
159
            // 动态调用命名范围
160
            $method = 'scope' . $method;
161
            array_unshift($args, $this);
162
163
            call_user_func_array([$this->model, $method], $args);
164
            return $this;
165
        } else {
166
            throw new Exception('method not exist:' . static::class . '->' . $method);
167
        }
168
    }
169
170
    /**
171
     * 获取当前的数据库Connection对象
172
     * @access public
173
     * @return Connection
174
     */
175
    public function getConnection()
176
    {
177
        return $this->connection;
178
    }
179
180
    /**
181
     * 设置当前的数据库Connection对象
182
     * @access public
183
     * @param  Connection      $connection 数据库连接对象
184
     * @return $this
185
     */
186
    public function setConnection($connection)
187
    {
188
        $this->connection = $connection;
189
190
        return $this;
191
    }
192
193
    /**
194
     * 指定模型
195
     * @access public
196
     * @param  Model $model 模型对象实例
197
     * @return $this
198
     */
199
    public function model(Model $model)
200
    {
201
        $this->model = $model;
202
        return $this;
203
    }
204
205
    /**
206
     * 获取当前的模型对象
207
     * @access public
208
     * @return Model|null
209
     */
210
    public function getModel()
211
    {
212
        return $this->model ? $this->model->setQuery($this) : null;
213
    }
214
215
    /**
216
     * 指定当前数据表名(不含前缀)
217
     * @access public
218
     * @param  string $name 不含前缀的数据表名字
219
     * @return $this
220
     */
221
    public function name(string $name)
222
    {
223
        $this->name = $name;
224
        return $this;
225
    }
226
227
    /**
228
     * 获取当前的数据表名称
229
     * @access public
230
     * @return string
231
     */
232
    public function getName(): string
233
    {
234
        return $this->name ?: $this->model->getName();
235
    }
236
237
    /**
238
     * 获取数据库的配置参数
239
     * @access public
240
     * @param  string $name 参数名称
241
     * @return mixed
242
     */
243
    public function getConfig(string $name = '')
244
    {
245
        return $this->connection->getConfig($name);
246
    }
247
248
    /**
249
     * 得到当前或者指定名称的数据表
250
     * @access public
251
     * @param  string $name 不含前缀的数据表名字
252
     * @return string
253
     */
254
    public function getTable(string $name = ''): string
255
    {
256
        if (empty($name) && isset($this->options['table'])) {
257
            return $this->options['table'];
258
        }
259
260
        $name = $name ?: $this->name;
261
262
        return $this->prefix . App::parseName($name);
263
    }
264
265
    /**
266
     * 获取数据表字段信息
267
     * @access public
268
     * @param  string $tableName 数据表名
269
     * @return array
270
     */
271
    public function getTableFields($tableName = ''): array
272
    {
273
        if ('' == $tableName) {
274
            $tableName = $this->options['table'] ?? $this->getTable();
275
        }
276
277
        return $this->connection->getTableFields($tableName);
278
    }
279
280
    /**
281
     * 设置字段类型信息
282
     * @access public
283
     * @param  array $type 字段类型信息
284
     * @return $this
285
     */
286
    public function setFieldType(array $type)
287
    {
288
        $this->options['field_type'] = $type;
289
        return $this;
290
    }
291
292
    /**
293
     * 获取字段类型信息
294
     * @access public
295
     * @return array
296
     */
297
    public function getFieldsType(): array
298
    {
299
        if (!empty($this->options['field_type'])) {
300
            return $this->options['field_type'];
301
        }
302
303
        $tableName = $this->options['table'] ?? $this->getTable();
304
305
        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...
306
    }
307
308
    /**
309
     * 获取字段类型信息
310
     * @access public
311
     * @param  string $field 字段名
312
     * @return string|null
313
     */
314
    public function getFieldType(string $field)
315
    {
316
        $fieldType = $this->getFieldsType();
317
318
        return $fieldType[$field] ?? null;
319
    }
320
321
    /**
322
     * 获取字段类型信息
323
     * @access public
324
     * @return array
325
     */
326
    public function getFieldsBindType(): array
327
    {
328
        $fieldType = $this->getFieldsType();
329
330
        return array_map([$this->connection, 'getFieldBindType'], $fieldType);
331
    }
332
333
    /**
334
     * 获取字段类型信息
335
     * @access public
336
     * @param  string $field 字段名
337
     * @return int
338
     */
339
    public function getFieldBindType(string $field): int
340
    {
341
        $fieldType = $this->getFieldType($field);
342
343
        return $this->connection->getFieldBindType($fieldType ?: '');
344
    }
345
346
    /**
347
     * 执行查询 返回数据集
348
     * @access public
349
     * @param  string $sql    sql指令
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 4 found
Loading history...
350
     * @param  array  $bind   参数绑定
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
351
     * @return array
352
     * @throws BindParamException
353
     * @throws PDOException
354
     */
355
    public function query(string $sql, array $bind = []): array
356
    {
357
        return $this->connection->query($this, $sql, $bind, true);
358
    }
359
360
    /**
361
     * 执行语句
362
     * @access public
363
     * @param  string $sql  sql指令
364
     * @param  array  $bind 参数绑定
365
     * @return int
366
     * @throws BindParamException
367
     * @throws PDOException
368
     */
369
    public function execute(string $sql, array $bind = []): int
370
    {
371
        return $this->connection->execute($this, $sql, $bind);
372
    }
373
374
    /**
375
     * 监听SQL执行
376
     * @access public
377
     * @param  callable $callback 回调方法
378
     * @return void
379
     */
380
    public function listen(callable $callback): void
381
    {
382
        $this->connection->listen($callback);
383
    }
384
385
    /**
386
     * 获取最近插入的ID
387
     * @access public
388
     * @param  string $sequence 自增序列名
389
     * @return string
390
     */
391
    public function getLastInsID(string $sequence = null): string
392
    {
393
        return $this->connection->getLastInsID($sequence);
394
    }
395
396
    /**
397
     * 获取返回或者影响的记录数
398
     * @access public
399
     * @return integer
400
     */
401
    public function getNumRows(): int
402
    {
403
        return $this->connection->getNumRows();
404
    }
405
406
    /**
407
     * 获取最近一次查询的sql语句
408
     * @access public
409
     * @return string
410
     */
411
    public function getLastSql(): string
412
    {
413
        return $this->connection->getLastSql();
414
    }
415
416
    /**
417
     * 执行数据库事务
418
     * @access public
419
     * @param  callable $callback 数据操作方法回调
420
     * @return mixed
421
     */
422
    public function transaction(callable $callback)
423
    {
424
        return $this->connection->transaction($callback);
425
    }
426
427
    /**
428
     * 启动事务
429
     * @access public
430
     * @return void
431
     */
432
    public function startTrans(): void
433
    {
434
        $this->connection->startTrans();
435
    }
436
437
    /**
438
     * 用于非自动提交状态下面的查询提交
439
     * @access public
440
     * @return void
441
     * @throws PDOException
442
     */
443
    public function commit(): void
444
    {
445
        $this->connection->commit();
446
    }
447
448
    /**
449
     * 事务回滚
450
     * @access public
451
     * @return void
452
     * @throws PDOException
453
     */
454
    public function rollback(): void
455
    {
456
        $this->connection->rollback();
457
    }
458
459
    /**
460
     * 批处理执行SQL语句
461
     * 批处理的指令都认为是execute操作
462
     * @access public
463
     * @param  array $sql SQL批处理指令
464
     * @return bool
465
     */
466
    public function batchQuery(array $sql = []): bool
467
    {
468
        return $this->connection->batchQuery($this, $sql);
469
    }
470
471
    /**
472
     * 得到某个字段的值
473
     * @access public
474
     * @param  string $field   字段名
475
     * @param  mixed  $default 默认值
476
     * @return mixed
477
     */
478
    public function value(string $field, $default = null)
479
    {
480
        return $this->connection->value($this, $field, $default);
481
    }
482
483
    /**
484
     * 得到某个列的数组
485
     * @access public
486
     * @param  string $field 字段名 多个字段用逗号分隔
487
     * @param  string $key   索引
488
     * @return array
489
     */
490
    public function column(string $field, string $key = ''): array
491
    {
492
        return $this->connection->column($this, $field, $key);
493
    }
494
495
    /**
496
     * 聚合查询
497
     * @access protected
498
     * @param  string     $aggregate    聚合方法
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
499
     * @param  string|Raw $field        字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
500
     * @param  bool       $force        强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
501
     * @return mixed
502
     */
503
    protected function aggregate(string $aggregate, $field, bool $force = false)
504
    {
505
        return $this->connection->aggregate($this, $aggregate, $field, $force);
506
    }
507
508
    /**
509
     * COUNT查询
510
     * @access public
511
     * @param  string|Raw $field 字段名
512
     * @return int
513
     */
514
    public function count(string $field = '*'): int
515
    {
516
        if (!empty($this->options['group'])) {
517
            // 支持GROUP
518
            $options = $this->getOptions();
519
            $subSql  = $this->options($options)->field('count(' . $field . ') AS think_count')->bind($this->bind)->buildSql();
520
521
            $query = $this->newQuery()->table([$subSql => '_group_count_']);
522
523
            $count = $query->aggregate('COUNT', '*');
524
        } else {
525
            $count = $this->aggregate('COUNT', $field);
526
        }
527
528
        return (int) $count;
529
    }
530
531
    /**
532
     * SUM查询
533
     * @access public
534
     * @param  string|Raw $field 字段名
535
     * @return float
536
     */
537
    public function sum($field): float
538
    {
539
        return $this->aggregate('SUM', $field, true);
540
    }
541
542
    /**
543
     * MIN查询
544
     * @access public
545
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
546
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
547
     * @return mixed
548
     */
549
    public function min($field, bool $force = true)
550
    {
551
        return $this->aggregate('MIN', $field, $force);
552
    }
553
554
    /**
555
     * MAX查询
556
     * @access public
557
     * @param  string|Raw $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
558
     * @param  bool       $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
559
     * @return mixed
560
     */
561
    public function max($field, bool $force = true)
562
    {
563
        return $this->aggregate('MAX', $field, $force);
564
    }
565
566
    /**
567
     * AVG查询
568
     * @access public
569
     * @param  string|Raw $field 字段名
570
     * @return float
571
     */
572
    public function avg($field): float
573
    {
574
        return $this->aggregate('AVG', $field, true);
575
    }
576
577
    /**
578
     * 查询SQL组装 join
579
     * @access public
580
     * @param  mixed  $join      关联的表名
581
     * @param  mixed  $condition 条件
582
     * @param  string $type      JOIN类型
583
     * @param  array  $bind      参数绑定
584
     * @return $this
585
     */
586
    public function join($join, string $condition = null, string $type = 'INNER', array $bind = [])
587
    {
588
        $table = $this->getJoinTable($join);
589
590
        if (!empty($bind) && $condition) {
591
            $this->bindParams($condition, $bind);
592
        }
593
594
        $this->options['join'][] = [$table, strtoupper($type), $condition];
595
596
        return $this;
597
    }
598
599
    /**
600
     * LEFT JOIN
601
     * @access public
602
     * @param  mixed  $join      关联的表名
603
     * @param  mixed  $condition 条件
604
     * @param  array  $bind      参数绑定
605
     * @return $this
606
     */
607
    public function leftJoin($join, string $condition = null, array $bind = [])
608
    {
609
        return $this->join($join, $condition, 'LEFT', $bind);
610
    }
611
612
    /**
613
     * RIGHT JOIN
614
     * @access public
615
     * @param  mixed  $join      关联的表名
616
     * @param  mixed  $condition 条件
617
     * @param  array  $bind      参数绑定
618
     * @return $this
619
     */
620
    public function rightJoin($join, string $condition = null, array $bind = [])
621
    {
622
        return $this->join($join, $condition, 'RIGHT', $bind);
623
    }
624
625
    /**
626
     * FULL JOIN
627
     * @access public
628
     * @param  mixed  $join      关联的表名
629
     * @param  mixed  $condition 条件
630
     * @param  array  $bind      参数绑定
631
     * @return $this
632
     */
633
    public function fullJoin($join, string $condition = null, array $bind = [])
634
    {
635
        return $this->join($join, $condition, 'FULL');
636
    }
637
638
    /**
639
     * 获取Join表名及别名 支持
640
     * ['prefix_table或者子查询'=>'alias'] 'table alias'
641
     * @access protected
642
     * @param  array|string|Raw $join  JION表名
643
     * @param  string           $alias 别名
644
     * @return string|array
645
     */
646
    protected function getJoinTable($join, &$alias = null)
647
    {
648
        if (is_array($join)) {
649
            $table = $join;
650
            $alias = array_shift($join);
651
            return $table;
652
        } elseif ($join instanceof Raw) {
653
            return $join;
654
        }
655
656
        $join = trim($join);
657
658
        if (false !== strpos($join, '(')) {
659
            // 使用子查询
660
            $table = $join;
661
        } else {
662
            // 使用别名
663
            if (strpos($join, ' ')) {
664
                // 使用别名
665
                list($table, $alias) = explode(' ', $join);
666
            } else {
667
                $table = $join;
668
                if (false === strpos($join, '.')) {
669
                    $alias = $join;
670
                }
671
            }
672
673
            if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) {
674
                $table = $this->getTable($table);
675
            }
676
        }
677
678
        if (!empty($alias) && $table != $alias) {
679
            $table = [$table => $alias];
680
        }
681
682
        return $table;
683
    }
684
685
    /**
686
     * 查询SQL组装 union
687
     * @access public
688
     * @param  mixed   $union UNION
689
     * @param  boolean $all   是否适用UNION ALL
690
     * @return $this
691
     */
692
    public function union($union, bool $all = false)
693
    {
694
        $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
695
696
        if (is_array($union)) {
697
            $this->options['union'] = array_merge($this->options['union'], $union);
698
        } else {
699
            $this->options['union'][] = $union;
700
        }
701
702
        return $this;
703
    }
704
705
    /**
706
     * 查询SQL组装 union all
707
     * @access public
708
     * @param  mixed   $union UNION数据
709
     * @return $this
710
     */
711
    public function unionAll($union)
712
    {
713
        return $this->union($union, true);
714
    }
715
716
    /**
717
     * 指定查询字段 支持字段排除和指定数据表
718
     * @access public
719
     * @param  mixed   $field     字段信息
720
     * @param  boolean $except    是否排除
721
     * @param  string  $tableName 数据表名
722
     * @param  string  $prefix    字段前缀
723
     * @param  string  $alias     别名前缀
724
     * @return $this
725
     */
726
    public function field($field, bool $except = false, string $tableName = '', string $prefix = '', string $alias = '')
727
    {
728
        if (empty($field)) {
729
            return $this;
730
        } elseif ($field instanceof Raw) {
731
            $this->options['field'][] = $field;
732
            return $this;
733
        }
734
735
        if (is_string($field)) {
736
            if (preg_match('/[\<\'\"\(]/', $field)) {
737
                return $this->fieldRaw($field);
738
            }
739
740
            $field = array_map('trim', explode(',', $field));
741
        }
742
743
        if (true === $field) {
744
            // 获取全部字段
745
            $fields = $this->getTableFields($tableName);
746
            $field  = $fields ?: ['*'];
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
747
        } elseif ($except) {
748
            // 字段排除
749
            $fields = $this->getTableFields($tableName);
750
            $field  = $fields ? array_diff($fields, $field) : $field;
0 ignored issues
show
introduced by
$fields is an empty array, thus is always false.
Loading history...
751
        }
752
753
        if ($tableName) {
754
            // 添加统一的前缀
755
            $prefix = $prefix ?: $tableName;
756
            foreach ($field as $key => &$val) {
757
                if (is_numeric($key) && $alias) {
758
                    $field[$prefix . '.' . $val] = $alias . $val;
759
                    unset($field[$key]);
760
                } elseif (is_numeric($key)) {
761
                    $val = $prefix . '.' . $val;
762
                }
763
            }
764
        }
765
766
        if (isset($this->options['field'])) {
767
            $field = array_merge((array) $this->options['field'], $field);
768
        }
769
770
        $this->options['field'] = array_unique($field);
771
772
        return $this;
773
    }
774
775
    /**
776
     * 表达式方式指定查询字段
777
     * @access public
778
     * @param  string $field 字段名
779
     * @return $this
780
     */
781
    public function fieldRaw(string $field)
782
    {
783
        $this->options['field'][] = new Raw($field);
784
785
        return $this;
786
    }
787
788
    /**
789
     * 设置数据
790
     * @access public
791
     * @param  array $data 数据
792
     * @return $this
793
     */
794
    public function data(array $data)
795
    {
796
        $this->options['data'] = $data;
797
798
        return $this;
799
    }
800
801
    /**
802
     * 字段值增长
803
     * @access public
804
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
805
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
806
     * @param  integer $lazyTime 延时时间(s)
807
     * @param  string  $op INC/DEC
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter name; 1 found
Loading history...
808
     * @return $this
809
     */
810
    public function inc(string $field, int $step = 1, int $lazyTime = 0, string $op = 'INC')
811
    {
812
        if ($lazyTime > 0) {
813
            // 延迟写入
814
            $condition = $this->options['where'] ?? [];
815
816
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
817
            $step = $this->connection->lazyWrite($op, $guid, $step, $lazyTime);
818
819
            if (false === $step) {
820
                return $this;
821
            }
822
823
            $op = 'INC';
824
        }
825
826
        $this->options['data'][$field] = [$op, $step];
827
828
        return $this;
829
    }
830
831
    /**
832
     * 字段值减少
833
     * @access public
834
     * @param  string  $field 字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
835
     * @param  integer $step  增长值
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 2 found
Loading history...
836
     * @param  integer $lazyTime 延时时间(s)
837
     * @return $this
838
     */
839
    public function dec(string $field, int $step = 1, int $lazyTime = 0)
840
    {
841
        return $this->inc($field, $step, $lazyTime, 'DEC');
842
    }
843
844
    /**
845
     * 使用表达式设置数据
846
     * @access public
847
     * @param  string $field 字段名
848
     * @param  string $value 字段值
849
     * @return $this
850
     */
851
    public function exp(string $field, string $value)
852
    {
853
        $this->options['data'][$field] = new Raw($value);
854
        return $this;
855
    }
856
857
    /**
858
     * 指定JOIN查询字段
859
     * @access public
860
     * @param  string|array $join  数据表
861
     * @param  string|array $field 查询字段
862
     * @param  string       $on    JOIN条件
863
     * @param  string       $type  JOIN类型
864
     * @param  array        $bind  参数绑定
865
     * @return $this
866
     */
867
    public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = [])
868
    {
869
        $this->options['view'] = true;
870
871
        $fields = [];
872
        $table  = $this->getJoinTable($join, $alias);
873
874
        if (true === $field) {
875
            $fields = $alias . '.*';
876
        } else {
877
            if (is_string($field)) {
878
                $field = explode(',', $field);
879
            }
880
881
            foreach ($field as $key => $val) {
882
                if (is_numeric($key)) {
883
                    $fields[] = $alias . '.' . $val;
884
885
                    $this->options['map'][$val] = $alias . '.' . $val;
886
                } else {
887
                    if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
888
                        $name = $key;
889
                    } else {
890
                        $name = $alias . '.' . $key;
891
                    }
892
893
                    $fields[] = $name . ' AS ' . $val;
894
895
                    $this->options['map'][$val] = $name;
896
                }
897
            }
898
        }
899
900
        $this->field($fields);
901
902
        if ($on) {
903
            $this->join($table, $on, $type, $bind);
904
        } else {
905
            $this->table($table);
906
        }
907
908
        return $this;
909
    }
910
911
    /**
912
     * 指定AND查询条件
913
     * @access public
914
     * @param  mixed $field     查询字段
915
     * @param  mixed $op        查询表达式
916
     * @param  mixed $condition 查询条件
917
     * @return $this
918
     */
919
    public function where($field, $op = null, $condition = null)
920
    {
921
        if ($field instanceof $this) {
922
            $this->options['where'] = $field->getOptions('where');
923
            return $this;
924
        }
925
926
        $param = func_get_args();
927
        array_shift($param);
928
        return $this->parseWhereExp('AND', $field, $op, $condition, $param);
929
    }
930
931
    /**
932
     * 指定OR查询条件
933
     * @access public
934
     * @param  mixed $field     查询字段
935
     * @param  mixed $op        查询表达式
936
     * @param  mixed $condition 查询条件
937
     * @return $this
938
     */
939
    public function whereOr($field, $op = null, $condition = null)
940
    {
941
        $param = func_get_args();
942
        array_shift($param);
943
        return $this->parseWhereExp('OR', $field, $op, $condition, $param);
944
    }
945
946
    /**
947
     * 指定XOR查询条件
948
     * @access public
949
     * @param  mixed $field     查询字段
950
     * @param  mixed $op        查询表达式
951
     * @param  mixed $condition 查询条件
952
     * @return $this
953
     */
954
    public function whereXor($field, $op = null, $condition = null)
955
    {
956
        $param = func_get_args();
957
        array_shift($param);
958
        return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
959
    }
960
961
    /**
962
     * 指定Null查询条件
963
     * @access public
964
     * @param  mixed  $field 查询字段
965
     * @param  string $logic 查询逻辑 and or xor
966
     * @return $this
967
     */
968
    public function whereNull(string $field, string $logic = 'AND')
969
    {
970
        return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
971
    }
972
973
    /**
974
     * 指定NotNull查询条件
975
     * @access public
976
     * @param  mixed  $field 查询字段
977
     * @param  string $logic 查询逻辑 and or xor
978
     * @return $this
979
     */
980
    public function whereNotNull(string $field, string $logic = 'AND')
981
    {
982
        return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
983
    }
984
985
    /**
986
     * 指定Exists查询条件
987
     * @access public
988
     * @param  mixed  $condition 查询条件
989
     * @param  string $logic     查询逻辑 and or xor
990
     * @return $this
991
     */
992
    public function whereExists($condition, string $logic = 'AND')
993
    {
994
        if (is_string($condition)) {
995
            $condition = new Raw($condition);
996
        }
997
998
        $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
999
        return $this;
1000
    }
1001
1002
    /**
1003
     * 指定NotExists查询条件
1004
     * @access public
1005
     * @param  mixed  $condition 查询条件
1006
     * @param  string $logic     查询逻辑 and or xor
1007
     * @return $this
1008
     */
1009
    public function whereNotExists($condition, string $logic = 'AND')
1010
    {
1011
        if (is_string($condition)) {
1012
            $condition = new Raw($condition);
1013
        }
1014
1015
        $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
1016
        return $this;
1017
    }
1018
1019
    /**
1020
     * 指定In查询条件
1021
     * @access public
1022
     * @param  mixed  $field     查询字段
1023
     * @param  mixed  $condition 查询条件
1024
     * @param  string $logic     查询逻辑 and or xor
1025
     * @return $this
1026
     */
1027
    public function whereIn(string $field, $condition, string $logic = 'AND')
1028
    {
1029
        return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
1030
    }
1031
1032
    /**
1033
     * 指定NotIn查询条件
1034
     * @access public
1035
     * @param  mixed  $field     查询字段
1036
     * @param  mixed  $condition 查询条件
1037
     * @param  string $logic     查询逻辑 and or xor
1038
     * @return $this
1039
     */
1040
    public function whereNotIn(string $field, $condition, string $logic = 'AND')
1041
    {
1042
        return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
1043
    }
1044
1045
    /**
1046
     * 指定Like查询条件
1047
     * @access public
1048
     * @param  mixed  $field     查询字段
1049
     * @param  mixed  $condition 查询条件
1050
     * @param  string $logic     查询逻辑 and or xor
1051
     * @return $this
1052
     */
1053
    public function whereLike(string $field, $condition, string $logic = 'AND')
1054
    {
1055
        return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
1056
    }
1057
1058
    /**
1059
     * 指定NotLike查询条件
1060
     * @access public
1061
     * @param  mixed  $field     查询字段
1062
     * @param  mixed  $condition 查询条件
1063
     * @param  string $logic     查询逻辑 and or xor
1064
     * @return $this
1065
     */
1066
    public function whereNotLike(string $field, $condition, string $logic = 'AND')
1067
    {
1068
        return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
1069
    }
1070
1071
    /**
1072
     * 指定Between查询条件
1073
     * @access public
1074
     * @param  mixed  $field     查询字段
1075
     * @param  mixed  $condition 查询条件
1076
     * @param  string $logic     查询逻辑 and or xor
1077
     * @return $this
1078
     */
1079
    public function whereBetween(string $field, $condition, string $logic = 'AND')
1080
    {
1081
        return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
1082
    }
1083
1084
    /**
1085
     * 指定NotBetween查询条件
1086
     * @access public
1087
     * @param  mixed  $field     查询字段
1088
     * @param  mixed  $condition 查询条件
1089
     * @param  string $logic     查询逻辑 and or xor
1090
     * @return $this
1091
     */
1092
    public function whereNotBetween(string $field, $condition, string $logic = 'AND')
1093
    {
1094
        return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
1095
    }
1096
1097
    /**
1098
     * 指定FIND_IN_SET查询条件
1099
     * @access public
1100
     * @param  mixed  $field     查询字段
1101
     * @param  mixed  $condition 查询条件
1102
     * @param  string $logic     查询逻辑 and or xor
1103
     * @return $this
1104
     */
1105
    public function whereFindInSet(string $field, $condition, string $logic = 'AND')
1106
    {
1107
        return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true);
1108
    }
1109
1110
    /**
1111
     * 比较两个字段
1112
     * @access public
1113
     * @param  string $field1     查询字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1114
     * @param  string $operator   比较操作符
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
1115
     * @param  string $field2     比较字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1116
     * @param  string $logic      查询逻辑 and or xor
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 6 found
Loading history...
1117
     * @return $this
1118
     */
1119
    public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
1120
    {
1121
        if (is_null($field2)) {
1122
            $field2   = $operator;
1123
            $operator = '=';
1124
        }
1125
1126
        return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
1127
    }
1128
1129
    /**
1130
     * 设置软删除字段及条件
1131
     * @access public
1132
     * @param  string       $field     查询字段
1133
     * @param  mixed        $condition 查询条件
1134
     * @return $this
1135
     */
1136
    public function useSoftDelete(string $field, $condition = null)
1137
    {
1138
        if ($field) {
1139
            $this->options['soft_delete'] = [$field, $condition];
1140
        }
1141
1142
        return $this;
1143
    }
1144
1145
    /**
1146
     * 指定Exp查询条件
1147
     * @access public
1148
     * @param  mixed  $field 查询字段
1149
     * @param  string $where 查询条件
1150
     * @param  array  $bind  参数绑定
1151
     * @param  string $logic 查询逻辑 and or xor
1152
     * @return $this
1153
     */
1154
    public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
1155
    {
1156
        if (!empty($bind)) {
1157
            $this->bindParams($where, $bind);
1158
        }
1159
1160
        $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where)];
1161
1162
        return $this;
1163
    }
1164
1165
    /**
1166
     * 指定字段Raw查询
1167
     * @access public
1168
     * @param  string $field     查询字段表达式
1169
     * @param  mixed  $op        查询表达式
1170
     * @param  string $condition 查询条件
1171
     * @param  string $logic     查询逻辑 and or xor
1172
     * @return $this
1173
     */
1174
    public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
1175
    {
1176
        if (is_null($condition)) {
1177
            $condition = $op;
1178
            $op        = '=';
1179
        }
1180
1181
        $this->options['where'][$logic][] = [new Raw($field), $op, $condition];
1182
        return $this;
1183
    }
1184
1185
    /**
1186
     * 指定表达式查询条件
1187
     * @access public
1188
     * @param  string $where 查询条件
1189
     * @param  array  $bind  参数绑定
1190
     * @param  string $logic 查询逻辑 and or xor
1191
     * @return $this
1192
     */
1193
    public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
1194
    {
1195
        if (!empty($bind)) {
1196
            $this->bindParams($where, $bind);
1197
        }
1198
1199
        $this->options['where'][$logic][] = new Raw($where);
1200
1201
        return $this;
1202
    }
1203
1204
    /**
1205
     * 指定表达式查询条件 OR
1206
     * @access public
1207
     * @param  string $where 查询条件
1208
     * @param  array  $bind  参数绑定
1209
     * @return $this
1210
     */
1211
    public function whereOrRaw(string $where, array $bind = [])
1212
    {
1213
        return $this->whereRaw($where, $bind, 'OR');
1214
    }
1215
1216
    /**
1217
     * 分析查询表达式
1218
     * @access protected
1219
     * @param  string $logic     查询逻辑 and or xor
1220
     * @param  mixed  $field     查询字段
1221
     * @param  mixed  $op        查询表达式
1222
     * @param  mixed  $condition 查询条件
1223
     * @param  array  $param     查询参数
1224
     * @param  bool   $strict    严格模式
1225
     * @return $this
1226
     */
1227
    protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
1228
    {
1229
        $logic = strtoupper($logic);
1230
1231
        if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) {
1232
            $field = $this->options['via'] . '.' . $field;
1233
        }
1234
1235
        if ($field instanceof Raw) {
1236
            return $this->whereRaw($field, is_array($op) ? $op : []);
1237
        } elseif ($strict) {
1238
            // 使用严格模式查询
1239
            if ('=' == $op) {
1240
                $where = $this->whereEq($field, $condition);
1241
            } else {
1242
                $where = [$field, $op, $condition, $logic];
1243
            }
1244
        } elseif (is_array($field)) {
1245
            // 解析数组批量查询
1246
            return $this->parseArrayWhereItems($field, $logic);
1247
        } elseif ($field instanceof Closure) {
1248
            $where = $field;
1249
        } elseif (is_string($field)) {
1250
            if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
1251
                return $this->whereRaw($field, is_array($op) ? $op : []);
1252
            } elseif (is_string($op) && strtolower($op) == 'exp') {
1253
                $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
1254
                return $this->whereExp($field, $condition, $bind, $logic);
1255
            }
1256
1257
            $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
1258
        }
1259
1260
        if (!empty($where)) {
1261
            $this->options['where'][$logic][] = $where;
1262
        }
1263
1264
        return $this;
1265
    }
1266
1267
    /**
1268
     * 分析查询表达式
1269
     * @access protected
1270
     * @param  string   $logic     查询逻辑 and or xor
1271
     * @param  mixed    $field     查询字段
1272
     * @param  mixed    $op        查询表达式
1273
     * @param  mixed    $condition 查询条件
1274
     * @param  array    $param     查询参数
1275
     * @return array
1276
     */
1277
    protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
1278
    {
1279
        if (is_array($op)) {
1280
            // 同一字段多条件查询
1281
            array_unshift($param, $field);
1282
            $where = $param;
1283
        } elseif (!is_string($op)) {
1284
            $where = $this->whereEq($field, $op);
1285
        } elseif ($field && is_null($condition)) {
1286
            if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
1287
                // null查询
1288
                $where = [$field, $op, ''];
1289
            } elseif ('=' == $op) {
1290
                $where = [$field, 'NULL', ''];
1291
            } elseif ('<>' == $op) {
1292
                $where = [$field, 'NOTNULL', ''];
1293
            } else {
1294
                // 字段相等查询
1295
                $where = $this->whereEq($field, $op);
1296
            }
1297
        } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
1298
            $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
1299
        } else {
1300
            $where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
1301
        }
1302
1303
        return $where;
1304
    }
1305
1306
    /**
1307
     * 相等查询的主键处理
1308
     * @access protected
1309
     * @param  string $field  字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1310
     * @param  mixed  $value  字段值
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1311
     * @return array
1312
     */
1313
    protected function whereEq(string $field, $value): array
1314
    {
1315
        $where = [$field, '=', $value];
1316
        if ($this->getPk() == $field) {
1317
            $this->options['key'] = $value;
1318
        }
1319
1320
        return $where;
1321
    }
1322
1323
    /**
1324
     * 数组批量查询
1325
     * @access protected
1326
     * @param  array  $field 批量查询
1327
     * @param  string $logic 查询逻辑 and or xor
1328
     * @return $this
1329
     */
1330
    protected function parseArrayWhereItems(array $field, string $logic)
1331
    {
1332
        if (key($field) !== 0) {
1333
            $where = [];
1334
            foreach ($field as $key => $val) {
1335
                if ($val instanceof Raw) {
1336
                    $where[] = [$key, 'exp', $val];
1337
                } else {
1338
                    $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
1339
                }
1340
            }
1341
        } else {
1342
            // 数组批量查询
1343
            $where = $field;
1344
        }
1345
1346
        if (!empty($where)) {
1347
            $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
1348
        }
1349
1350
        return $this;
1351
    }
1352
1353
    /**
1354
     * 去除某个查询条件
1355
     * @access public
1356
     * @param  string $field 查询字段
1357
     * @param  string $logic 查询逻辑 and or xor
1358
     * @return $this
1359
     */
1360
    public function removeWhereField(string $field, string $logic = 'AND')
1361
    {
1362
        $logic = strtoupper($logic);
1363
1364
        if (isset($this->options['where'][$logic])) {
1365
            foreach ($this->options['where'][$logic] as $key => $val) {
1366
                if (is_array($val) && $val[0] == $field) {
1367
                    unset($this->options['where'][$logic][$key]);
1368
                }
1369
            }
1370
        }
1371
1372
        return $this;
1373
    }
1374
1375
    /**
1376
     * 去除查询参数
1377
     * @access public
1378
     * @param  string $option 参数名 留空去除所有参数
1379
     * @return $this
1380
     */
1381
    public function removeOption(string $option = '')
1382
    {
1383
        if ('' === $option) {
1384
            $this->options = [];
1385
            $this->bind    = [];
1386
        } elseif (isset($this->options[$option])) {
1387
            unset($this->options[$option]);
1388
        }
1389
1390
        return $this;
1391
    }
1392
1393
    /**
1394
     * 条件查询
1395
     * @access public
1396
     * @param  mixed         $condition  满足条件(支持闭包)
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1397
     * @param  Closure|array $query      满足条件后执行的查询表达式(闭包或数组)
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 6 found
Loading history...
1398
     * @param  Closure|array $otherwise  不满足条件后执行
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1399
     * @return $this
1400
     */
1401
    public function when($condition, $query, $otherwise = null)
1402
    {
1403
        if ($condition instanceof Closure) {
1404
            $condition = $condition($this);
1405
        }
1406
1407
        if ($condition) {
1408
            if ($query instanceof Closure) {
1409
                $query($this, $condition);
1410
            } elseif (is_array($query)) {
0 ignored issues
show
introduced by
The condition is_array($query) is always true.
Loading history...
1411
                $this->where($query);
1412
            }
1413
        } elseif ($otherwise) {
1414
            if ($otherwise instanceof Closure) {
1415
                $otherwise($this, $condition);
1416
            } elseif (is_array($otherwise)) {
0 ignored issues
show
introduced by
The condition is_array($otherwise) is always true.
Loading history...
1417
                $this->where($otherwise);
1418
            }
1419
        }
1420
1421
        return $this;
1422
    }
1423
1424
    /**
1425
     * 指定查询数量
1426
     * @access public
1427
     * @param  int $offset 起始位置
1428
     * @param  int $length 查询数量
1429
     * @return $this
1430
     */
1431
    public function limit(int $offset, int $length = null)
1432
    {
1433
        $this->options['limit'] = $offset . ($length ? ',' . $length : '');
1434
1435
        return $this;
1436
    }
1437
1438
    /**
1439
     * 指定分页
1440
     * @access public
1441
     * @param  int $page     页数
1442
     * @param  int $listRows 每页数量
1443
     * @return $this
1444
     */
1445
    public function page(int $page, int $listRows = null)
1446
    {
1447
        $this->options['page'] = [$page, $listRows];
1448
1449
        return $this;
1450
    }
1451
1452
    /**
1453
     * 分页查询
1454
     * @access public
1455
     * @param  int|array $listRows 每页数量 数组表示配置参数
1456
     * @param  int|bool  $simple   是否简洁模式或者总记录数
1457
     * @param  array     $config   配置参数
1458
     * @return \think\Paginator
1459
     * @throws DbException
1460
     */
1461
    public function paginate($listRows = null, $simple = false, $config = [])
1462
    {
1463
        if (is_int($simple)) {
1464
            $total  = $simple;
1465
            $simple = false;
1466
        }
1467
1468
        $paginate = array_merge($this->paginateConfig, Container::pull('config')->get('paginate'));
1469
1470
        if (is_array($listRows)) {
1471
            $config   = array_merge($paginate, $listRows);
1472
            $listRows = intval($config['list_rows']);
1473
        } else {
1474
            $config   = array_merge($paginate, $config);
1475
            $listRows = intval($listRows ?: $config['list_rows']);
1476
        }
1477
1478
        $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
1479
        $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...
1480
            $class,
1481
            'getCurrentPage',
1482
        ], $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...
1483
1484
        $page = $page < 1 ? 1 : $page;
1485
1486
        $config['path'] = $config['path'] ?? call_user_func([$class, 'getCurrentPath']);
1487
1488
        if (!isset($total) && !$simple) {
1489
            $options = $this->getOptions();
1490
1491
            unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
1492
1493
            $bind    = $this->bind;
1494
            $total   = $this->count();
1495
            $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
1496
        } elseif ($simple) {
1497
            $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
1498
            $total   = null;
1499
        } else {
1500
            $results = $this->page($page, $listRows)->select();
1501
        }
1502
1503
        $this->removeOption('limit');
1504
        $this->removeOption('page');
1505
1506
        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...
1507
    }
1508
1509
    /**
1510
     * 表达式方式指定当前操作的数据表
1511
     * @access public
1512
     * @param  mixed $table 表名
1513
     * @return $this
1514
     */
1515
    public function tableRaw(string $table)
1516
    {
1517
        $this->options['table'] = new Raw($table);
1518
1519
        return $this;
1520
    }
1521
1522
    /**
1523
     * 指定当前操作的数据表
1524
     * @access public
1525
     * @param  mixed $table 表名
1526
     * @return $this
1527
     */
1528
    public function table($table)
1529
    {
1530
        if (is_string($table)) {
1531
            if (strpos($table, ')')) {
1532
                // 子查询
1533
            } else {
1534
                $tables = explode(',', $table);
1535
                $table  = [];
1536
1537
                foreach ($tables as $item) {
1538
                    $item = trim($item);
1539
                    if (strpos($item, ' ')) {
1540
                        list($item, $alias) = explode(' ', $item);
1541
                        $this->alias([$item => $alias]);
1542
                        $table[$item] = $alias;
1543
                    } else {
1544
                        $table[] = $item;
1545
                    }
1546
                }
1547
            }
1548
        } elseif (is_array($table)) {
1549
            $tables = $table;
1550
            $table  = [];
1551
1552
            foreach ($tables as $key => $val) {
1553
                if (is_numeric($key)) {
1554
                    $table[] = $val;
1555
                } else {
1556
                    $this->alias([$key => $val]);
1557
                    $table[$key] = $val;
1558
                }
1559
            }
1560
        }
1561
1562
        $this->options['table'] = $table;
1563
1564
        return $this;
1565
    }
1566
1567
    /**
1568
     * USING支持 用于多表删除
1569
     * @access public
1570
     * @param  mixed $using USING
1571
     * @return $this
1572
     */
1573
    public function using($using)
1574
    {
1575
        $this->options['using'] = $using;
1576
        return $this;
1577
    }
1578
1579
    /**
1580
     * 存储过程调用
1581
     * @access public
1582
     * @param  bool $procedure 是否为存储过程查询
1583
     * @return $this
1584
     */
1585
    public function procedure($procedure = true)
1586
    {
1587
        $this->options['procedure'] = $procedure;
1588
        return $this;
1589
    }
1590
1591
    /**
1592
     * 是否允许返回空数据(或空模型)
1593
     * @access public
1594
     * @param  bool $allowEmpty 是否允许为空
1595
     * @return $this
1596
     */
1597
    public function allowEmpty(bool $allowEmpty = true)
1598
    {
1599
        $this->options['allow_empty'] = $allowEmpty;
1600
        return $this;
1601
    }
1602
1603
    /**
1604
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
1605
     * @access public
1606
     * @param  string|array|Raw $field 排序字段
1607
     * @param  string           $order 排序
1608
     * @return $this
1609
     */
1610
    public function order($field, string $order = '')
1611
    {
1612
        if (empty($field)) {
1613
            return $this;
1614
        } elseif ($field instanceof Raw) {
1615
            $this->options['order'][] = $field;
1616
            return $this;
1617
        }
1618
1619
        if (is_string($field)) {
1620
            if (!empty($this->options['via'])) {
1621
                $field = $this->options['via'] . '.' . $field;
1622
            }
1623
            if (strpos($field, ',')) {
1624
                $field = array_map('trim', explode(',', $field));
1625
            } else {
1626
                $field = empty($order) ? $field : [$field => $order];
1627
            }
1628
        } elseif (!empty($this->options['via'])) {
1629
            foreach ($field as $key => $val) {
1630
                if (is_numeric($key)) {
1631
                    $field[$key] = $this->options['via'] . '.' . $val;
1632
                } else {
1633
                    $field[$this->options['via'] . '.' . $key] = $val;
1634
                    unset($field[$key]);
1635
                }
1636
            }
1637
        }
1638
1639
        if (!isset($this->options['order'])) {
1640
            $this->options['order'] = [];
1641
        }
1642
1643
        if (is_array($field)) {
1644
            $this->options['order'] = array_merge($this->options['order'], $field);
1645
        } else {
1646
            $this->options['order'][] = $field;
1647
        }
1648
1649
        return $this;
1650
    }
1651
1652
    /**
1653
     * 表达式方式指定Field排序
1654
     * @access public
1655
     * @param  string $field 排序字段
1656
     * @param  array  $bind  参数绑定
1657
     * @return $this
1658
     */
1659
    public function orderRaw(string $field, array $bind = [])
1660
    {
1661
        if (!empty($bind)) {
1662
            $this->bindParams($field, $bind);
1663
        }
1664
1665
        $this->options['order'][] = new Raw($field);
1666
1667
        return $this;
1668
    }
1669
1670
    /**
1671
     * 指定Field排序 orderField('id',[1,2,3],'desc')
1672
     * @access public
1673
     * @param  string $field 排序字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1674
     * @param  array  $values 排序值
1675
     * @param  string $order 排序 desc/asc
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1676
     * @return $this
1677
     */
1678
    public function orderField(string $field, array $values, string $order = '')
1679
    {
1680
        if (!empty($values)) {
1681
            $values['sort'] = $order;
1682
1683
            $this->options['order'][$field] = $values;
1684
        }
1685
1686
        return $this;
1687
    }
1688
1689
    /**
1690
     * 随机排序
1691
     * @access public
1692
     * @return $this
1693
     */
1694
    public function orderRand()
1695
    {
1696
        $this->options['order'][] = '[rand]';
1697
        return $this;
1698
    }
1699
1700
    /**
1701
     * 查询缓存
1702
     * @access public
1703
     * @param  mixed             $key    缓存key
1704
     * @param  integer|\DateTime $expire 缓存有效期
1705
     * @param  string            $tag    缓存标签
1706
     * @return $this
1707
     */
1708
    public function cache($key = true, $expire = null, $tag = null)
1709
    {
1710
        if (false === $key) {
1711
            return $this;
1712
        }
1713
1714
        if ($key instanceof \DateTimeInterface || (is_int($key) && is_null($expire))) {
1715
            $expire = $key;
1716
            $key    = true;
1717
        }
1718
1719
        $this->options['cache'] = [$key, $expire, $tag];
1720
1721
        return $this;
1722
    }
1723
1724
    /**
1725
     * 指定group查询
1726
     * @access public
1727
     * @param  string|array $group GROUP
1728
     * @return $this
1729
     */
1730
    public function group($group)
1731
    {
1732
        $this->options['group'] = $group;
1733
        return $this;
1734
    }
1735
1736
    /**
1737
     * 指定having查询
1738
     * @access public
1739
     * @param  string $having having
1740
     * @return $this
1741
     */
1742
    public function having(string $having)
1743
    {
1744
        $this->options['having'] = $having;
1745
        return $this;
1746
    }
1747
1748
    /**
1749
     * 指定查询lock
1750
     * @access public
1751
     * @param  bool|string $lock 是否lock
1752
     * @return $this
1753
     */
1754
    public function lock($lock = false)
1755
    {
1756
        $this->options['lock'] = $lock;
1757
1758
        if ($lock) {
1759
            $this->options['master'] = true;
1760
        }
1761
1762
        return $this;
1763
    }
1764
1765
    /**
1766
     * 指定distinct查询
1767
     * @access public
1768
     * @param  bool $distinct 是否唯一
1769
     * @return $this
1770
     */
1771
    public function distinct(bool $distinct = true)
1772
    {
1773
        $this->options['distinct'] = $distinct;
1774
        return $this;
1775
    }
1776
1777
    /**
1778
     * 指定数据表别名
1779
     * @access public
1780
     * @param  array|string $alias 数据表别名
1781
     * @return $this
1782
     */
1783
    public function alias($alias)
1784
    {
1785
        if (is_array($alias)) {
1786
            $this->options['alias'] = $alias;
1787
        } else {
1788
            $table = $this->getTable();
1789
1790
            $this->options['alias'][$table] = $alias;
1791
        }
1792
1793
        return $this;
1794
    }
1795
1796
    /**
1797
     * 指定强制索引
1798
     * @access public
1799
     * @param  string $force 索引名称
1800
     * @return $this
1801
     */
1802
    public function force(string $force)
1803
    {
1804
        $this->options['force'] = $force;
1805
        return $this;
1806
    }
1807
1808
    /**
1809
     * 查询注释
1810
     * @access public
1811
     * @param  string $comment 注释
1812
     * @return $this
1813
     */
1814
    public function comment(string $comment)
1815
    {
1816
        $this->options['comment'] = $comment;
1817
        return $this;
1818
    }
1819
1820
    /**
1821
     * 获取执行的SQL语句而不进行实际的查询
1822
     * @access public
1823
     * @param  bool $fetch 是否返回sql
1824
     * @return $this|Fetch
1825
     */
1826
    public function fetchSql(bool $fetch = true)
1827
    {
1828
        $this->options['fetch_sql'] = $fetch;
1829
1830
        if ($fetch) {
1831
            return new Fetch($this);
1832
        }
1833
1834
        return $this;
1835
    }
1836
1837
    /**
1838
     * 设置是否返回数据集对象
1839
     * @access public
1840
     * @param  bool|string $collection 是否返回数据集对象
1841
     * @return $this
1842
     */
1843
    public function fetchCollection($collection = true)
1844
    {
1845
        $this->options['collection'] = $collection;
1846
        return $this;
1847
    }
1848
1849
    /**
1850
     * 设置是否返回数组
1851
     * @access public
1852
     * @param  bool $asArray 是否返回数组
1853
     * @return $this
1854
     */
1855
    public function fetchArray(bool $asArray = true)
1856
    {
1857
        $this->options['array'] = $asArray;
1858
        return $this;
1859
    }
1860
1861
    /**
1862
     * 设置从主服务器读取数据
1863
     * @access public
1864
     * @param  bool $readMaster 是否从主服务器读取
1865
     * @return $this
1866
     */
1867
    public function master(bool $readMaster = true)
1868
    {
1869
        $this->options['master'] = $readMaster;
1870
        return $this;
1871
    }
1872
1873
    /**
1874
     * 设置是否严格检查字段名
1875
     * @access public
1876
     * @param  bool $strict 是否严格检查字段
1877
     * @return $this
1878
     */
1879
    public function strict(bool $strict = true)
1880
    {
1881
        $this->options['strict'] = $strict;
1882
        return $this;
1883
    }
1884
1885
    /**
1886
     * 设置查询数据不存在是否抛出异常
1887
     * @access public
1888
     * @param  bool $fail 数据不存在是否抛出异常
1889
     * @return $this
1890
     */
1891
    public function failException(bool $fail = true)
1892
    {
1893
        $this->options['fail'] = $fail;
1894
        return $this;
1895
    }
1896
1897
    /**
1898
     * 设置自增序列名
1899
     * @access public
1900
     * @param  string $sequence 自增序列名
1901
     * @return $this
1902
     */
1903
    public function sequence(string $sequence = null)
1904
    {
1905
        $this->options['sequence'] = $sequence;
1906
        return $this;
1907
    }
1908
1909
    /**
1910
     * 设置是否REPLACE
1911
     * @access public
1912
     * @param  bool $replace 是否使用REPLACE写入数据
1913
     * @return $this
1914
     */
1915
    public function replace(bool $replace = true)
1916
    {
1917
        $this->options['replace'] = $replace;
1918
        return $this;
1919
    }
1920
1921
    /**
1922
     * 设置当前查询所在的分区
1923
     * @access public
1924
     * @param  string|array $partition 分区名称
1925
     * @return $this
1926
     */
1927
    public function partition($partition)
1928
    {
1929
        $this->options['partition'] = $partition;
1930
        return $this;
1931
    }
1932
1933
    /**
1934
     * 设置DUPLICATE
1935
     * @access public
1936
     * @param  array|string|Raw $duplicate DUPLICATE信息
1937
     * @return $this
1938
     */
1939
    public function duplicate($duplicate)
1940
    {
1941
        $this->options['duplicate'] = $duplicate;
1942
        return $this;
1943
    }
1944
1945
    /**
1946
     * 设置查询的额外参数
1947
     * @access public
1948
     * @param  string $extra 额外信息
1949
     * @return $this
1950
     */
1951
    public function extra(string $extra)
1952
    {
1953
        $this->options['extra'] = $extra;
1954
        return $this;
1955
    }
1956
1957
    /**
1958
     * 设置需要隐藏的输出属性
1959
     * @access public
1960
     * @param  array $hidden 需要隐藏的字段名
1961
     * @return $this
1962
     */
1963
    public function hidden(array $hidden)
1964
    {
1965
        $this->options['hidden'] = $hidden;
1966
        return $this;
1967
    }
1968
1969
    /**
1970
     * 设置需要输出的属性
1971
     * @access public
1972
     * @param  array $visible 需要输出的属性
1973
     * @return $this
1974
     */
1975
    public function visible(array $visible)
1976
    {
1977
        $this->options['visible'] = $visible;
1978
        return $this;
1979
    }
1980
1981
    /**
1982
     * 设置需要追加输出的属性
1983
     * @access public
1984
     * @param  array $append 需要追加的属性
1985
     * @return $this
1986
     */
1987
    public function append(array $append)
1988
    {
1989
        $this->options['append'] = $append;
1990
        return $this;
1991
    }
1992
1993
    /**
1994
     * 设置JSON字段信息
1995
     * @access public
1996
     * @param  array $json JSON字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1997
     * @param  bool  $assoc 是否取出数组
1998
     * @return $this
1999
     */
2000
    public function json(array $json = [], bool $assoc = false)
2001
    {
2002
        $this->options['json']       = $json;
2003
        $this->options['json_assoc'] = $assoc;
2004
        return $this;
2005
    }
2006
2007
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
2008
     * 添加查询范围
2009
     * @access public
2010
     * @param  array|string|Closure $scope 查询范围定义
2011
     * @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...
2012
     * @return $this
2013
     */
2014
    public function scope($scope, ...$args)
2015
    {
2016
        // 查询范围的第一个参数始终是当前查询对象
2017
        array_unshift($args, $this);
2018
2019
        if ($scope instanceof Closure) {
2020
            call_user_func_array($scope, $args);
2021
            return $this;
2022
        }
2023
2024
        if (is_string($scope)) {
2025
            $scope = explode(',', $scope);
2026
        }
2027
2028
        if ($this->model) {
2029
            // 检查模型类的查询范围方法
2030
            foreach ($scope as $name) {
2031
                $method = 'scope' . trim($name);
2032
2033
                if (method_exists($this->model, $method)) {
2034
                    call_user_func_array([$this->model, $method], $args);
2035
                }
2036
            }
2037
        }
2038
2039
        return $this;
2040
    }
2041
2042
    /**
2043
     * 指定数据表主键
2044
     * @access public
2045
     * @param  string $pk 主键
2046
     * @return $this
2047
     */
2048
    public function pk(string $pk)
2049
    {
2050
        $this->pk = $pk;
2051
        return $this;
2052
    }
2053
2054
    /**
2055
     * 添加日期或者时间查询规则
2056
     * @access public
2057
     * @param  string       $name 时间表达式
2058
     * @param  string|array $rule 时间范围
2059
     * @return $this
2060
     */
2061
    public function timeRule(string $name, $rule)
2062
    {
2063
        $this->timeRule[$name] = $rule;
2064
        return $this;
2065
    }
2066
2067
    /**
2068
     * 查询日期或者时间
2069
     * @access public
2070
     * @param  string       $field 日期字段名
2071
     * @param  string       $op    比较运算符或者表达式
2072
     * @param  string|array $range 比较范围
2073
     * @param  string       $logic AND OR
2074
     * @return $this
2075
     */
2076
    public function whereTime(string $field, string $op, $range = null, string $logic = 'AND')
2077
    {
2078
        if (is_null($range) && isset($this->timeRule[$op])) {
2079
            $range = $this->timeRule[$op];
2080
            $op    = 'between';
2081
        }
2082
2083
        return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
2084
    }
2085
2086
    /**
2087
     * 查询某个时间间隔数据
2088
     * @access protected
2089
     * @param  string $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2090
     * @param  string $start 开始时间
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2091
     * @param  string $interval 时间间隔单位
2092
     * @param  string $logic AND OR
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2093
     * @return $this
2094
     */
2095
    protected function whereTimeInterval(string $field, string $start, string $interval = 'day', string $logic = 'AND')
2096
    {
2097
        $startTime = strtotime($start);
2098
        $endTime   = strtotime('+1 ' . $interval, $startTime);
2099
2100
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2101
    }
2102
2103
    /**
2104
     * 查询月数据 whereMonth('time_field', '2018-1')
2105
     * @access public
2106
     * @param  string $field 日期字段名
2107
     * @param  string $month 月份信息
2108
     * @param  string $logic AND OR
2109
     * @return $this
2110
     */
2111
    public function whereMonth(string $field, string $month = 'this month', string $logic = 'AND')
2112
    {
2113
        if (in_array($month, ['this month', 'last month'])) {
2114
            $month = date('Y-m', strtotime($month));
2115
        }
2116
2117
        return $this->whereTimeInterval($field, $month, 'month', $logic);
2118
    }
2119
2120
    /**
2121
     * 查询年数据 whereYear('time_field', '2018')
2122
     * @access public
2123
     * @param  string $field 日期字段名
2124
     * @param  string $year  年份信息
2125
     * @param  string $logic AND OR
2126
     * @return $this
2127
     */
2128
    public function whereYear(string $field, string $year = 'this year', string $logic = 'AND')
2129
    {
2130
        if (in_array($year, ['this year', 'last year'])) {
2131
            $year = date('Y', strtotime($year));
2132
        }
2133
2134
        return $this->whereTimeInterval($field, $year . '-1-1', 'year', $logic);
2135
    }
2136
2137
    /**
2138
     * 查询日数据 whereDay('time_field', '2018-1-1')
2139
     * @access public
2140
     * @param  string $field 日期字段名
2141
     * @param  string $day   日期信息
2142
     * @param  string $logic AND OR
2143
     * @return $this
2144
     */
2145
    public function whereDay(string $field, string $day = 'today', string $logic = 'AND')
2146
    {
2147
        if (in_array($day, ['today', 'yesterday'])) {
2148
            $day = date('Y-m-d', strtotime($day));
2149
        }
2150
2151
        return $this->whereTimeInterval($field, $day, 'day', $logic);
2152
    }
2153
2154
    /**
2155
     * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15')
2156
     * @access public
2157
     * @param  string     $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2158
     * @param  string|int $startTime    开始时间
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2159
     * @param  string|int $endTime 结束时间
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2160
     * @param  string     $logic AND OR
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2161
     * @return $this
2162
     */
2163
    public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND')
2164
    {
2165
        return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
2166
    }
2167
2168
    /**
2169
     * 查询日期或者时间范围 whereNotBetweenTime('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
     * @return $this
2175
     */
2176
    public function whereNotBetweenTime(string $field, $startTime, $endTime)
2177
    {
2178
        return $this->whereTime($field, '<', $startTime)
2179
            ->whereTime($field, '>', $endTime);
2180
    }
2181
2182
    /**
2183
     * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time')
2184
     * @access public
2185
     * @param  string $startField 开始时间字段
2186
     * @param  string $endField   结束时间字段
2187
     * @return $this
2188
     */
2189
    public function whereBetweenTimeField(string $startField, string $endField)
2190
    {
2191
        return $this->whereTime($startField, '<=', time())
2192
            ->whereTime($endField, '>=', time());
2193
    }
2194
2195
    /**
2196
     * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time')
2197
     * @access public
2198
     * @param  string $startField 开始时间字段
2199
     * @param  string $endField   结束时间字段
2200
     * @return $this
2201
     */
2202
    public function whereNotBetweenTimeField(string $startField, string $endField)
2203
    {
2204
        return $this->whereTime($startField, '>', time())
2205
            ->whereTime($endField, '<', time(), 'OR');
2206
    }
2207
2208
    /**
2209
     * 获取当前数据表的主键
2210
     * @access public
2211
     * @return string|array
2212
     */
2213
    public function getPk()
2214
    {
2215
        if (!empty($this->pk)) {
2216
            $pk = $this->pk;
2217
        } else {
2218
            $this->pk = $pk = $this->connection->getPk($this->getTable());
2219
        }
2220
2221
        return $pk;
2222
    }
2223
2224
    /**
2225
     * 批量参数绑定
2226
     * @access public
2227
     * @param  array $value 绑定变量值
2228
     * @return $this
2229
     */
2230
    public function bind(array $value)
2231
    {
2232
        $this->bind = array_merge($this->bind, $value);
2233
        return $this;
2234
    }
2235
2236
    /**
2237
     * 单个参数绑定
2238
     * @access public
2239
     * @param  mixed   $value 绑定变量值
2240
     * @param  integer $type  绑定类型
2241
     * @param  string  $name  绑定标识
2242
     * @return string
2243
     */
2244
    public function bindValue($value, int $type = null, string $name = null)
2245
    {
2246
        $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_';
2247
2248
        $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR];
2249
        return $name;
2250
    }
2251
2252
    /**
2253
     * 检测参数是否已经绑定
2254
     * @access public
2255
     * @param  string $key 参数名
2256
     * @return bool
2257
     */
2258
    public function isBind($key)
2259
    {
2260
        return isset($this->bind[$key]);
2261
    }
2262
2263
    /**
2264
     * 参数绑定
2265
     * @access public
2266
     * @param  string $sql  绑定的sql表达式
2267
     * @param  array  $bind 参数绑定
2268
     * @return void
2269
     */
2270
    protected function bindParams(string &$sql, array $bind = []): void
2271
    {
2272
        foreach ($bind as $key => $value) {
2273
            if (is_array($value)) {
2274
                $name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
2275
            } else {
2276
                $name = $this->bindValue($value);
2277
            }
2278
2279
            if (is_numeric($key)) {
2280
                $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
2281
            } else {
2282
                $sql = str_replace(':' . $key, ':' . $name, $sql);
2283
            }
2284
        }
2285
    }
2286
2287
    /**
2288
     * 查询参数批量赋值
2289
     * @access protected
2290
     * @param  array $options 表达式参数
2291
     * @return $this
2292
     */
2293
    protected function options(array $options)
2294
    {
2295
        $this->options = $options;
2296
        return $this;
2297
    }
2298
2299
    /**
2300
     * 获取当前的查询参数
2301
     * @access public
2302
     * @param  string $name 参数名
2303
     * @return mixed
2304
     */
2305
    public function getOptions(string $name = '')
2306
    {
2307
        if ('' === $name) {
2308
            return $this->options;
2309
        }
2310
2311
        return $this->options[$name] ?? null;
2312
    }
2313
2314
    /**
2315
     * 设置当前的查询参数
2316
     * @access public
2317
     * @param  string $option 参数名
2318
     * @param  mixed  $value  参数值
2319
     * @return $this
2320
     */
2321
    public function setOption(string $option, $value)
2322
    {
2323
        $this->options[$option] = $value;
2324
        return $this;
2325
    }
2326
2327
    /**
2328
     * 设置关联查询
2329
     * @access public
2330
     * @param  array $relation 关联名称
2331
     * @return $this
2332
     */
2333
    public function relation(array $relation)
2334
    {
2335
        if (!empty($relation)) {
2336
            $this->options['relation'] = $relation;
2337
        }
2338
2339
        return $this;
2340
    }
2341
2342
    /**
2343
     * 设置关联查询JOIN预查询
2344
     * @access public
2345
     * @param  array $with 关联方法名称(数组)
2346
     * @return $this
2347
     */
2348
    public function with(array $with)
2349
    {
2350
        if (!empty($with)) {
2351
            $this->options['with'] = $with;
2352
        }
2353
2354
        return $this;
2355
    }
2356
2357
    /**
2358
     * 关联预载入 JOIN方式
2359
     * @access protected
2360
     * @param  array  $with 关联方法名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2361
     * @param  string $joinType JOIN方式
2362
     * @return $this
2363
     */
2364
    public function withJoin(array $with, string $joinType = '')
2365
    {
2366
        if (empty($with)) {
2367
            return $this;
2368
        }
2369
2370
        $first = true;
2371
2372
        /** @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...
2373
        $class = $this->model;
2374
        foreach ($with as $key => $relation) {
2375
            $closure = null;
2376
            $field   = true;
2377
2378
            if ($relation instanceof Closure) {
2379
                // 支持闭包查询过滤关联条件
2380
                $closure  = $relation;
2381
                $relation = $key;
2382
            } elseif (is_array($relation)) {
2383
                $field    = $relation;
2384
                $relation = $key;
2385
            } elseif (is_string($relation) && strpos($relation, '.')) {
2386
                $relation = strstr($relation, '.', true);
2387
            }
2388
2389
            /** @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...
2390
            $relation = App::parseName($relation, 1, false);
2391
            $model    = $class->$relation();
2392
2393
            if ($model instanceof OneToOne) {
2394
                $model->eagerly($this, $relation, $field, $joinType, $closure, $first);
2395
                $first = false;
2396
            } else {
2397
                // 不支持其它关联
2398
                unset($with[$key]);
2399
            }
2400
        }
2401
2402
        $this->via();
2403
2404
        $this->options['with_join'] = $with;
2405
2406
        return $this;
2407
    }
2408
2409
    /**
2410
     * 设置数据字段获取器
2411
     * @access public
2412
     * @param  string   $name     字段名
2413
     * @param  callable $callback 闭包获取器
2414
     * @return $this
2415
     */
2416
    public function withAttr(string $name, callable $callback)
2417
    {
2418
        $this->options['with_attr'][$name] = $callback;
2419
2420
        return $this;
2421
    }
2422
2423
    /**
2424
     * 设置数据字段获取器
2425
     * @access public
2426
     * @param  array $attrs 字段获取器
2427
     * @return $this
2428
     */
2429
    public function withAttrs(array $attrs)
2430
    {
2431
        $this->options['with_attr'] = $attrs;
2432
2433
        return $this;
2434
    }
2435
2436
    /**
2437
     * 使用搜索器条件搜索字段
2438
     * @access public
2439
     * @param  array  $fields 搜索字段
2440
     * @param  array  $data   搜索数据
2441
     * @param  string $prefix 字段前缀标识
2442
     * @return $this
2443
     */
2444
    public function withSearch(array $fields, array $data = [], string $prefix = '')
2445
    {
2446
        foreach ($fields as $key => $field) {
2447
            if ($field instanceof Closure) {
2448
                $field($this, $data[$key] ?? null, $data, $prefix);
2449
            } elseif ($this->model) {
2450
                // 检测搜索器
2451
                $fieldName = is_numeric($key) ? $field : $key;
2452
                $method    = 'search' . App::parseName($fieldName, 1) . 'Attr';
2453
2454
                if (method_exists($this->model, $method)) {
2455
                    $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
2456
                }
2457
            }
2458
        }
2459
2460
        return $this;
2461
    }
2462
2463
    /**
2464
     * 关联统计
2465
     * @access protected
2466
     * @param  array|string $relations 关联方法名
2467
     * @param  string       $aggregate 聚合查询方法
2468
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2469
     * @param  bool         $subQuery 是否使用子查询
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
2470
     * @return $this
2471
     */
2472
    protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
2473
    {
2474
        if (is_string($relations)) {
2475
            $relations = explode(',', $relations);
2476
        }
2477
2478
        if (!$subQuery) {
2479
            $this->options['with_count'][] = [$relations, $aggregate, $field];
2480
        } else {
2481
            if (!isset($this->options['field'])) {
2482
                $this->field('*');
2483
            }
2484
2485
            foreach ($relations as $key => $relation) {
2486
                $closure = $aggregateField = null;
2487
2488
                if ($relation instanceof Closure) {
2489
                    $closure  = $relation;
2490
                    $relation = $key;
2491
                } elseif (!is_int($key)) {
2492
                    $aggregateField = $relation;
2493
                    $relation       = $key;
2494
                }
2495
2496
                $relation = App::parseName($relation, 1, false);
2497
2498
                $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField) . ')';
2499
2500
                if (empty($aggregateField)) {
2501
                    $aggregateField = App::parseName($relation) . '_' . $aggregate;
2502
                }
2503
2504
                $this->field([$count => $aggregateField]);
2505
            }
2506
        }
2507
2508
        return $this;
2509
    }
2510
2511
    /**
2512
     * 关联统计
2513
     * @access public
2514
     * @param  string|array $relation 关联方法名
2515
     * @param  bool         $subQuery 是否使用子查询
2516
     * @return $this
2517
     */
2518
    public function withCount($relation, bool $subQuery = true)
2519
    {
2520
        return $this->withAggregate($relation, 'count', '*', $subQuery);
2521
    }
2522
2523
    /**
2524
     * 关联统计Sum
2525
     * @access public
2526
     * @param  string|array $relation 关联方法名
2527
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2528
     * @param  bool         $subQuery 是否使用子查询
2529
     * @return $this
2530
     */
2531
    public function withSum($relation, string $field, bool $subQuery = true)
2532
    {
2533
        return $this->withAggregate($relation, 'sum', $field, $subQuery);
2534
    }
2535
2536
    /**
2537
     * 关联统计Max
2538
     * @access public
2539
     * @param  string|array $relation 关联方法名
2540
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2541
     * @param  bool         $subQuery 是否使用子查询
2542
     * @return $this
2543
     */
2544
    public function withMax($relation, string $field, bool $subQuery = true)
2545
    {
2546
        return $this->withAggregate($relation, 'max', $field, $subQuery);
2547
    }
2548
2549
    /**
2550
     * 关联统计Min
2551
     * @access public
2552
     * @param  string|array $relation 关联方法名
2553
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2554
     * @param  bool         $subQuery 是否使用子查询
2555
     * @return $this
2556
     */
2557
    public function withMin($relation, string $field, bool $subQuery = true)
2558
    {
2559
        return $this->withAggregate($relation, 'min', $field, $subQuery);
2560
    }
2561
2562
    /**
2563
     * 关联统计Avg
2564
     * @access public
2565
     * @param  string|array $relation 关联方法名
2566
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2567
     * @param  bool         $subQuery 是否使用子查询
2568
     * @return $this
2569
     */
2570
    public function withAvg($relation, string $field, bool $subQuery = true)
2571
    {
2572
        return $this->withAggregate($relation, 'avg', $field, $subQuery);
2573
    }
2574
2575
    /**
2576
     * 关联预加载中 获取关联指定字段值
2577
     * example:
2578
     * Model::with(['relation' => function($query){
2579
     *     $query->withField("id,name");
2580
     * }])
2581
     *
2582
     * @access public
2583
     * @param  string|array $field 指定获取的字段
2584
     * @return $this
2585
     */
2586
    public function withField($field)
2587
    {
2588
        $this->options['with_field'] = $field;
2589
2590
        return $this;
2591
    }
2592
2593
    /**
2594
     * 设置当前字段添加的表别名
2595
     * @access public
2596
     * @param  string $via 临时表别名
2597
     * @return $this
2598
     */
2599
    public function via(string $via = '')
2600
    {
2601
        $this->options['via'] = $via;
2602
2603
        return $this;
2604
    }
2605
2606
    /**
2607
     * 保存记录 自动判断insert或者update
2608
     * @access public
2609
     * @param  array $data        数据
2610
     * @param  bool  $forceInsert 是否强制insert
2611
     * @return integer
2612
     */
2613
    public function save(array $data = [], bool $forceInsert = false)
2614
    {
2615
        if ($forceInsert) {
2616
            return $this->insert($data);
2617
        }
2618
2619
        if (!empty($data)) {
2620
            $this->options['data'] = $data;
2621
        }
2622
2623
        if (!empty($this->options['where'])) {
2624
            $isUpdate = true;
2625
        } else {
2626
            $isUpdate = $this->parseUpdateData($this->options['data']);
2627
        }
2628
2629
        return $isUpdate ? $this->update() : $this->insert();
2630
    }
2631
2632
    /**
2633
     * 插入记录
2634
     * @access public
2635
     * @param  array   $data         数据
2636
     * @param  boolean $getLastInsID 返回自增主键
2637
     * @param  string  $sequence     自增序列名
2638
     * @return integer
2639
     */
2640
    public function insert(array $data = [], bool $getLastInsID = false, string $sequence = null)
2641
    {
2642
        if (!empty($data)) {
2643
            $this->options['data'] = $data;
2644
        }
2645
2646
        $replace = $this->options['replace'] ?? false;
2647
        return $this->connection->insert($this, $replace, $getLastInsID, $sequence);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection...etLastInsID, $sequence) also could return the type string which is incompatible with the documented return type integer.
Loading history...
2648
    }
2649
2650
    /**
2651
     * 插入记录并获取自增ID
2652
     * @access public
2653
     * @param  array   $data     数据
2654
     * @param  string  $sequence 自增序列名
2655
     * @return integer|string
2656
     */
2657
    public function insertGetId(array $data, string $sequence = null)
2658
    {
2659
        return $this->insert($data, true, $sequence);
2660
    }
2661
2662
    /**
2663
     * 批量插入记录
2664
     * @access public
2665
     * @param  array   $dataSet 数据集
2666
     * @param  integer $limit   每次写入数据限制
2667
     * @return integer
2668
     */
2669
    public function insertAll(array $dataSet = [], int $limit = 0): int
2670
    {
2671
        if (empty($dataSet)) {
2672
            $dataSet = $this->options['data'] ?? [];
2673
        }
2674
2675
        if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
2676
            $limit = (int) $this->options['limit'];
2677
        }
2678
2679
        $replace = $this->options['replace'] ?? false;
2680
        return $this->connection->insertAll($this, $dataSet, $replace, $limit);
2681
    }
2682
2683
    /**
2684
     * 通过Select方式插入记录
2685
     * @access public
2686
     * @param  array  $fields 要插入的数据表字段名
2687
     * @param  string $table  要插入的数据表名
2688
     * @return integer
2689
     * @throws PDOException
2690
     */
2691
    public function selectInsert(array $fields, string $table): int
2692
    {
2693
        return $this->connection->selectInsert($this, $fields, $table);
2694
    }
2695
2696
    /**
2697
     * 更新记录
2698
     * @access public
2699
     * @param  mixed $data 数据
2700
     * @return integer
2701
     * @throws Exception
2702
     * @throws PDOException
2703
     */
2704
    public function update(array $data = []): int
2705
    {
2706
        if (empty($data)) {
2707
            $data = $this->options['data'] ?? [];
2708
        }
2709
2710
        if (empty($this->options['where'])) {
2711
            $this->parseUpdateData($data);
2712
        }
2713
2714
        if (empty($this->options['where']) && $this->model) {
2715
            $this->where($this->model->getWhere());
2716
        }
2717
2718
        if (empty($this->options['where'])) {
2719
            // 如果没有任何更新条件则不执行
2720
            throw new Exception('miss update condition');
2721
        }
2722
2723
        $this->options['data'] = $data;
2724
2725
        return $this->connection->update($this);
2726
    }
2727
2728
    /**
2729
     * 删除记录
2730
     * @access public
2731
     * @param  mixed $data 表达式 true 表示强制删除
2732
     * @return int
2733
     * @throws Exception
2734
     * @throws PDOException
2735
     */
2736
    public function delete($data = null): int
2737
    {
2738
        if (!is_null($data) && true !== $data) {
2739
            // AR模式分析主键条件
2740
            $this->parsePkWhere($data);
2741
        }
2742
2743
        if (empty($this->options['where']) && $this->model) {
2744
            $this->where($this->model->getWhere());
2745
        }
2746
2747
        if (true !== $data && empty($this->options['where'])) {
2748
            // 如果条件为空 不进行删除操作 除非设置 1=1
2749
            throw new Exception('delete without condition');
2750
        }
2751
2752
        if (!empty($this->options['soft_delete'])) {
2753
            // 软删除
2754
            list($field, $condition) = $this->options['soft_delete'];
2755
            if ($condition) {
2756
                unset($this->options['soft_delete']);
2757
                $this->options['data'] = [$field => $condition];
2758
2759
                return $this->connection->update($this);
2760
            }
2761
        }
2762
2763
        $this->options['data'] = $data;
2764
2765
        return $this->connection->delete($this);
2766
    }
2767
2768
    /**
2769
     * 执行查询但只返回PDOStatement对象
2770
     * @access public
2771
     * @return PDOStatement
2772
     */
2773
    public function getPdo(): PDOStatement
2774
    {
2775
        return $this->connection->pdo($this);
2776
    }
2777
2778
    /**
2779
     * 使用游标查找记录
2780
     * @access public
2781
     * @param  mixed $data 数据
2782
     * @return \Generator
2783
     */
2784
    public function cursor($data = null)
2785
    {
2786
        if (!is_null($data)) {
2787
            // 主键条件分析
2788
            $this->parsePkWhere($data);
2789
        }
2790
2791
        $this->options['data'] = $data;
2792
2793
        $connection = clone $this->connection;
2794
2795
        return $connection->cursor($this);
2796
    }
2797
2798
    /**
2799
     * 查找记录
2800
     * @access public
2801
     * @param  mixed $data 数据
2802
     * @return Collection|array|ModelCollection
2803
     * @throws DbException
2804
     * @throws ModelNotFoundException
2805
     * @throws DataNotFoundException
2806
     */
2807
    public function select($data = null)
2808
    {
2809
        if (!is_null($data)) {
2810
            // 主键条件分析
2811
            $this->parsePkWhere($data);
2812
        }
2813
2814
        $resultSet = $this->connection->select($this);
2815
2816
        // 返回结果处理
2817
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
2818
            $this->throwNotFound();
2819
        }
2820
2821
        // 数据列表读取后的处理
2822
        if (!empty($this->model) && empty($this->options['array'])) {
2823
            // 生成模型对象
2824
            $resultSet = $this->resultSetToModelCollection($resultSet);
2825
        } else {
2826
            $this->resultSet($resultSet);
2827
        }
2828
2829
        return $resultSet;
2830
    }
2831
2832
    /**
2833
     * 查询数据转换为模型数据集对象
2834
     * @access protected
2835
     * @param  array $resultSet 数据集
2836
     * @return ModelCollection
2837
     */
2838
    protected function resultSetToModelCollection(array $resultSet): ModelCollection
2839
    {
2840
        if (!empty($this->options['collection']) && is_string($this->options['collection'])) {
2841
            $collection = $this->options['collection'];
2842
        }
2843
2844
        if (empty($resultSet)) {
2845
            return $this->model->toCollection([], $collection ?? null);
2846
        }
2847
2848
        // 检查动态获取器
2849
        if (!empty($this->options['with_attr'])) {
2850
            foreach ($this->options['with_attr'] as $name => $val) {
2851
                if (strpos($name, '.')) {
2852
                    list($relation, $field) = explode('.', $name);
2853
2854
                    $withRelationAttr[$relation][$field] = $val;
2855
                    unset($this->options['with_attr'][$name]);
2856
                }
2857
            }
2858
        }
2859
2860
        $withRelationAttr = $withRelationAttr ?? [];
2861
2862
        foreach ($resultSet as $key => &$result) {
2863
            // 数据转换为模型对象
2864
            $this->resultToModel($result, $this->options, true, $withRelationAttr);
2865
        }
2866
2867
        if (!empty($this->options['with'])) {
2868
            // 预载入
2869
            $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 2862. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2870
        }
2871
2872
        if (!empty($this->options['with_join'])) {
2873
            // 预载入
2874
            $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true);
2875
        }
2876
2877
        // 模型数据集转换
2878
        return $this->model->toCollection($resultSet, $collection ?? null);
2879
    }
2880
2881
    /**
2882
     * 处理数据集
2883
     * @access public
2884
     * @param  array $resultSet 数据集
2885
     * @return void
2886
     */
2887
    protected function resultSet(array &$resultSet): void
2888
    {
2889
        if (!empty($this->options['json'])) {
2890
            foreach ($resultSet as &$result) {
2891
                $this->jsonResult($result, $this->options['json'], true);
2892
            }
2893
        }
2894
2895
        if (!empty($this->options['with_attr'])) {
2896
            foreach ($resultSet as &$result) {
2897
                $this->getResultAttr($result, $this->options['with_attr']);
2898
            }
2899
        }
2900
2901
        if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
2902
            foreach ($resultSet as &$result) {
2903
                $this->filterResult($result);
2904
            }
2905
        }
2906
2907
        if (!empty($this->options['collection'])) {
2908
            // 返回Collection对象
2909
            $resultSet = new Collection($resultSet);
2910
        }
2911
    }
2912
2913
    /**
2914
     * 查找单条记录
2915
     * @access public
2916
     * @param  mixed $data 查询数据
2917
     * @return array|Model|null
2918
     * @throws DbException
2919
     * @throws ModelNotFoundException
2920
     * @throws DataNotFoundException
2921
     */
2922
    public function find($data = null)
2923
    {
2924
        if (!is_null($data)) {
2925
            // AR模式分析主键条件
2926
            $this->parsePkWhere($data);
2927
        }
2928
2929
        $result = $this->connection->find($this);
2930
2931
        // 数据处理
2932
        if (empty($result)) {
2933
            return $this->resultToEmpty();
2934
        }
2935
2936
        if (!empty($this->model) && empty($this->options['array'])) {
2937
            // 返回模型对象
2938
            $this->resultToModel($result, $this->options);
2939
        } else {
2940
            $this->result($result);
2941
        }
2942
2943
        return $result;
2944
    }
2945
2946
    /**
2947
     * 查找单条记录 不存在返回空数据(或者空模型)
2948
     * @access public
2949
     * @param  mixed $data 数据
2950
     * @return array|Model
2951
     */
2952
    public function findOrEmpty($data = null)
2953
    {
2954
        return $this->allowEmpty(true)->find($data);
2955
    }
2956
2957
    /**
2958
     * 处理空数据
2959
     * @access protected
2960
     * @return array|Model|null
2961
     * @throws DbException
2962
     * @throws ModelNotFoundException
2963
     * @throws DataNotFoundException
2964
     */
2965
    protected function resultToEmpty()
2966
    {
2967
        if (!empty($this->options['fail'])) {
2968
            $this->throwNotFound();
2969
        } elseif (!empty($this->options['allow_empty'])) {
2970
            return !empty($this->model) && empty($this->options['array']) ? $this->model->newInstance([], true) : [];
2971
        } elseif (!empty($this->options['array'])) {
2972
            return [];
2973
        }
2974
    }
2975
2976
    /**
2977
     * 获取模型的更新条件
2978
     * @access protected
2979
     * @param  array $options 查询参数
2980
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
2981
    protected function getModelUpdateCondition(array $options)
2982
    {
2983
        return $options['where']['AND'] ?? null;
2984
    }
2985
2986
    /**
2987
     * 处理数据
2988
     * @access protected
2989
     * @param  array $result 查询数据
2990
     * @return void
2991
     */
2992
    protected function result(array &$result): void
2993
    {
2994
        if (!empty($this->options['json'])) {
2995
            $this->jsonResult($result, $this->options['json'], true);
2996
        }
2997
2998
        if (!empty($this->options['with_attr'])) {
2999
            $this->getResultAttr($result, $this->options['with_attr']);
3000
        }
3001
3002
        $this->filterResult($result);
3003
    }
3004
3005
    /**
3006
     * 处理数据的可见和隐藏
3007
     * @access protected
3008
     * @param  array $result 查询数据
3009
     * @return void
3010
     */
3011
    protected function filterResult(&$result): void
3012
    {
3013
        if (!empty($this->options['visible'])) {
3014
            foreach ($this->options['visible'] as $key) {
3015
                $array[] = $key;
3016
            }
3017
            $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 3014. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
3018
        } elseif (!empty($this->options['hidden'])) {
3019
            foreach ($this->options['hidden'] as $key) {
3020
                $array[] = $key;
3021
            }
3022
            $result = array_diff_key($result, array_flip($array));
3023
        }
3024
    }
3025
3026
    /**
3027
     * 使用获取器处理数据
3028
     * @access protected
3029
     * @param  array $result   查询数据
3030
     * @param  array $withAttr 字段获取器
3031
     * @return void
3032
     */
3033
    protected function getResultAttr(array &$result, array $withAttr = []): void
3034
    {
3035
        foreach ($withAttr as $name => $closure) {
3036
            $name = App::parseName($name);
3037
3038
            if (strpos($name, '.')) {
3039
                // 支持JSON字段 获取器定义
3040
                list($key, $field) = explode('.', $name);
3041
3042
                if (isset($result[$key])) {
3043
                    $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
3044
                }
3045
            } else {
3046
                $result[$name] = $closure($result[$name] ?? null, $result);
3047
            }
3048
        }
3049
    }
3050
3051
    /**
3052
     * JSON字段数据转换
3053
     * @access protected
3054
     * @param  array $result           查询数据
3055
     * @param  array $json             JSON字段
3056
     * @param  bool  $assoc            是否转换为数组
3057
     * @param  array $withRelationAttr 关联获取器
3058
     * @return void
3059
     */
3060
    protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
3061
    {
3062
        foreach ($json as $name) {
3063
            if (!isset($result[$name])) {
3064
                continue;
3065
            }
3066
3067
            $result[$name] = json_decode($result[$name], $assoc);
3068
3069
            if (!isset($withRelationAttr[$name])) {
3070
                continue;
3071
            }
3072
3073
            foreach ($withRelationAttr[$name] as $key => $closure) {
3074
                $data = get_object_vars($result[$name]);
3075
3076
                $result[$name]->$key = $closure($result[$name]->$key ?? null, $data);
3077
            }
3078
        }
3079
    }
3080
3081
    /**
3082
     * 查询数据转换为模型对象
3083
     * @access protected
3084
     * @param  array $result           查询数据
3085
     * @param  array $options          查询参数
3086
     * @param  bool  $resultSet        是否为数据集查询
3087
     * @param  array $withRelationAttr 关联字段获取器
3088
     * @return void
3089
     */
3090
    protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
3091
    {
3092
        // 动态获取器
3093
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
3094
            foreach ($options['with_attr'] as $name => $val) {
3095
                if (strpos($name, '.')) {
3096
                    list($relation, $field) = explode('.', $name);
3097
3098
                    $withRelationAttr[$relation][$field] = $val;
3099
                    unset($options['with_attr'][$name]);
3100
                }
3101
            }
3102
        }
3103
3104
        // JSON 数据处理
3105
        if (!empty($options['json'])) {
3106
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
3107
        }
3108
3109
        $result = $this->model->newInstance($result, true, $resultSet ? null : $this->getModelUpdateCondition($options));
3110
3111
        // 动态获取器
3112
        if (!empty($options['with_attr'])) {
3113
            $result->withAttribute($options['with_attr']);
3114
        }
3115
3116
        // 输出属性控制
3117
        if (!empty($options['visible'])) {
3118
            $result->visible($options['visible']);
3119
        } elseif (!empty($options['hidden'])) {
3120
            $result->hidden($options['hidden']);
3121
        }
3122
3123
        if (!empty($options['append'])) {
3124
            $result->append($options['append']);
3125
        }
3126
3127
        // 关联查询
3128
        if (!empty($options['relation'])) {
3129
            $result->relationQuery($options['relation']);
3130
        }
3131
3132
        // 预载入查询
3133
        if (!$resultSet && !empty($options['with'])) {
3134
            $result->eagerlyResult($result, $options['with'], $withRelationAttr);
3135
        }
3136
3137
        // JOIN预载入查询
3138
        if (!$resultSet && !empty($options['with_join'])) {
3139
            $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true);
3140
        }
3141
3142
        // 关联统计
3143
        if (!empty($options['with_count'])) {
3144
            foreach ($options['with_count'] as $val) {
3145
                $result->relationCount($result, $val[0], $val[1], $val[2]);
3146
            }
3147
        }
3148
    }
3149
3150
    /**
3151
     * 查询失败 抛出异常
3152
     * @access protected
3153
     * @return void
3154
     * @throws ModelNotFoundException
3155
     * @throws DataNotFoundException
3156
     */
3157
    protected function throwNotFound(): void
3158
    {
3159
        if (!empty($this->model)) {
3160
            $class = get_class($this->model);
3161
            throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options);
3162
        }
3163
3164
        $table = $this->getTable();
3165
        throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options);
3166
    }
3167
3168
    /**
3169
     * 查找多条记录 如果不存在则抛出异常
3170
     * @access public
3171
     * @param  array|string|Query|Closure $data 数据
3172
     * @return array|PDOStatement|string|Model
3173
     * @throws DbException
3174
     * @throws ModelNotFoundException
3175
     * @throws DataNotFoundException
3176
     */
3177
    public function selectOrFail($data = null)
3178
    {
3179
        return $this->failException(true)->select($data);
3180
    }
3181
3182
    /**
3183
     * 查找单条记录 如果不存在则抛出异常
3184
     * @access public
3185
     * @param  array|string|Query|Closure $data 数据
3186
     * @return array|PDOStatement|string|Model
3187
     * @throws DbException
3188
     * @throws ModelNotFoundException
3189
     * @throws DataNotFoundException
3190
     */
3191
    public function findOrFail($data = null)
3192
    {
3193
        return $this->failException(true)->find($data);
3194
    }
3195
3196
    /**
3197
     * 分批数据返回处理
3198
     * @access public
3199
     * @param  integer      $count    每次处理的数据数量
3200
     * @param  callable     $callback 处理回调方法
3201
     * @param  string|array $column   分批处理的字段名
3202
     * @param  string       $order    字段排序
3203
     * @return bool
3204
     * @throws DbException
3205
     */
3206
    public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
3207
    {
3208
        $options = $this->getOptions();
3209
        $column  = $column ?: $this->getPk();
3210
3211
        if (isset($options['order'])) {
3212
            if (Container::pull('app')->isDebug()) {
3213
                throw new DbException('chunk not support call order');
3214
            }
3215
            unset($options['order']);
3216
        }
3217
3218
        $bind = $this->bind;
3219
3220
        if (is_array($column)) {
3221
            $times = 1;
3222
            $query = $this->options($options)->page($times, $count);
3223
        } else {
3224
            $query = $this->options($options)->limit($count);
3225
3226
            if (strpos($column, '.')) {
3227
                list($alias, $key) = explode('.', $column);
3228
            } else {
3229
                $key = $column;
3230
            }
3231
        }
3232
3233
        $resultSet = $query->order($column, $order)->select();
3234
3235
        while (count($resultSet) > 0) {
3236
            if ($resultSet instanceof Collection) {
3237
                $resultSet = $resultSet->all();
3238
            }
3239
3240
            if (false === call_user_func($callback, $resultSet)) {
3241
                return false;
3242
            }
3243
3244
            if (isset($times)) {
3245
                $times++;
3246
                $query = $this->options($options)->page($times, $count);
3247
            } else {
3248
                $end    = end($resultSet);
3249
                $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...
3250
3251
                $query = $this->options($options)
3252
                    ->limit($count)
3253
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
3254
            }
3255
3256
            $resultSet = $query->bind($bind)->order($column, $order)->select();
3257
        }
3258
3259
        return true;
3260
    }
3261
3262
    /**
3263
     * 获取绑定的参数 并清空
3264
     * @access public
3265
     * @param  bool $clear 是否清空绑定数据
3266
     * @return array
3267
     */
3268
    public function getBind(bool $clear = true): array
3269
    {
3270
        $bind = $this->bind;
3271
        if ($clear) {
3272
            $this->bind = [];
3273
        }
3274
3275
        return $bind;
3276
    }
3277
3278
    /**
3279
     * 创建子查询SQL
3280
     * @access public
3281
     * @param  bool $sub 是否添加括号
3282
     * @return string
3283
     * @throws DbException
3284
     */
3285
    public function buildSql(bool $sub = true): string
3286
    {
3287
        return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
3288
    }
3289
3290
    /**
3291
     * 视图查询处理
3292
     * @access protected
3293
     * @param  array $options 查询参数
3294
     * @return void
3295
     */
3296
    protected function parseView(array &$options): void
3297
    {
3298
        foreach (['AND', 'OR'] as $logic) {
3299
            if (isset($options['where'][$logic])) {
3300
                foreach ($options['where'][$logic] as $key => $val) {
3301
                    if (array_key_exists($key, $options['map'])) {
3302
                        array_shift($val);
3303
                        array_unshift($val, $options['map'][$key]);
3304
                        $options['where'][$logic][$options['map'][$key]] = $val;
3305
                        unset($options['where'][$logic][$key]);
3306
                    }
3307
                }
3308
            }
3309
        }
3310
3311
        if (isset($options['order'])) {
3312
            // 视图查询排序处理
3313
            foreach ($options['order'] as $key => $val) {
3314
                if (is_numeric($key) && is_string($val)) {
3315
                    if (strpos($val, ' ')) {
3316
                        list($field, $sort) = explode(' ', $val);
3317
                        if (array_key_exists($field, $options['map'])) {
3318
                            $options['order'][$options['map'][$field]] = $sort;
3319
                            unset($options['order'][$key]);
3320
                        }
3321
                    } elseif (array_key_exists($val, $options['map'])) {
3322
                        $options['order'][$options['map'][$val]] = 'asc';
3323
                        unset($options['order'][$key]);
3324
                    }
3325
                } elseif (array_key_exists($key, $options['map'])) {
3326
                    $options['order'][$options['map'][$key]] = $val;
3327
                    unset($options['order'][$key]);
3328
                }
3329
            }
3330
        }
3331
    }
3332
3333
    /**
3334
     * 分析数据是否存在更新条件
3335
     * @access public
3336
     * @param  array $data 数据
3337
     * @return bool
3338
     * @throws Exception
3339
     */
3340
    public function parseUpdateData(&$data): bool
3341
    {
3342
        $pk       = $this->getPk();
3343
        $isUpdate = false;
3344
        // 如果存在主键数据 则自动作为更新条件
3345
        if (is_string($pk) && isset($data[$pk])) {
3346
            $this->where($pk, '=', $data[$pk]);
3347
            $this->options['key'] = $data[$pk];
3348
            unset($data[$pk]);
3349
            $isUpdate = true;
3350
        } elseif (is_array($pk)) {
3351
            // 增加复合主键支持
3352
            foreach ($pk as $field) {
3353
                if (isset($data[$field])) {
3354
                    $this->where($field, '=', $data[$field]);
3355
                    $isUpdate = true;
3356
                } else {
3357
                    // 如果缺少复合主键数据则不执行
3358
                    throw new Exception('miss complex primary data');
3359
                }
3360
                unset($data[$field]);
3361
            }
3362
        }
3363
3364
        return $isUpdate;
3365
    }
3366
3367
    /**
3368
     * 把主键值转换为查询条件 支持复合主键
3369
     * @access public
3370
     * @param  array|string $data 主键数据
3371
     * @return void
3372
     * @throws Exception
3373
     */
3374
    public function parsePkWhere($data): void
3375
    {
3376
        $pk = $this->getPk();
3377
3378
        if (is_string($pk)) {
3379
            // 获取数据表
3380
            if (empty($this->options['table'])) {
3381
                $this->options['table'] = $this->getTable();
3382
            }
3383
3384
            $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
3385
3386
            if (!empty($this->options['alias'][$table])) {
3387
                $alias = $this->options['alias'][$table];
3388
            }
3389
3390
            $key = isset($alias) ? $alias . '.' . $pk : $pk;
3391
            // 根据主键查询
3392
            if (is_array($data)) {
3393
                $this->where($key, 'in', $data);
3394
            } else {
3395
                $this->where($key, '=', $data);
3396
                $this->options['key'] = $data;
3397
            }
3398
        }
3399
    }
3400
3401
    /**
3402
     * 分析表达式(可用于查询或者写入操作)
3403
     * @access public
3404
     * @return array
3405
     */
3406
    public function parseOptions(): array
3407
    {
3408
        $options = $this->getOptions();
3409
3410
        // 获取数据表
3411
        if (empty($options['table'])) {
3412
            $options['table'] = $this->getTable();
3413
        }
3414
3415
        if (!isset($options['where'])) {
3416
            $options['where'] = [];
3417
        } elseif (isset($options['view'])) {
3418
            // 视图查询条件处理
3419
            $this->parseView($options);
3420
        }
3421
3422
        if (!isset($options['field'])) {
3423
            $options['field'] = '*';
3424
        }
3425
3426
        foreach (['data', 'order', 'join', 'union'] as $name) {
3427
            if (!isset($options[$name])) {
3428
                $options[$name] = [];
3429
            }
3430
        }
3431
3432
        if (!isset($options['strict'])) {
3433
            $options['strict'] = $this->connection->getConfig('fields_strict');
3434
        }
3435
3436
        foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
3437
            if (!isset($options[$name])) {
3438
                $options[$name] = false;
3439
            }
3440
        }
3441
3442
        foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
3443
            if (!isset($options[$name])) {
3444
                $options[$name] = '';
3445
            }
3446
        }
3447
3448
        if (isset($options['page'])) {
3449
            // 根据页数计算limit
3450
            list($page, $listRows) = $options['page'];
3451
            $page                  = $page > 0 ? $page : 1;
3452
            $listRows              = $listRows ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
3453
            $offset                = $listRows * ($page - 1);
3454
            $options['limit']      = $offset . ',' . $listRows;
3455
        }
3456
3457
        $this->options = $options;
3458
3459
        return $options;
3460
    }
3461
3462
    /**
3463
     * 注册回调方法
3464
     * @access public
3465
     * @param  string   $event    事件名
3466
     * @param  callable $callback 回调方法
3467
     * @return void
3468
     */
3469
    public static function event(string $event, callable $callback): void
3470
    {
3471
        self::$event[$event] = $callback;
3472
    }
3473
3474
    /**
3475
     * 触发事件
3476
     * @access public
3477
     * @param  string $event 事件名
3478
     * @return mixed
3479
     */
3480
    public function trigger(string $event)
3481
    {
3482
        $result = false;
3483
3484
        if (isset(self::$event[$event])) {
3485
            $result = Container::getInstance()->invoke(self::$event[$event], [$this]);
3486
        }
3487
3488
        return $result;
3489
    }
3490
3491
}
3492