Passed
Pull Request — 5.1 (#1748)
by guanguans
09:27
created

Query::whereIn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 3
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~2018 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
12
namespace think\db;
13
14
use PDO;
15
use think\Collection;
16
use think\Container;
17
use think\Db;
18
use think\db\exception\BindParamException;
19
use think\db\exception\DataNotFoundException;
20
use think\db\exception\ModelNotFoundException;
21
use think\Exception;
22
use think\exception\DbException;
23
use think\exception\PDOException;
24
use think\Loader;
25
use think\Model;
26
use think\model\Collection as ModelCollection;
27
use think\model\Relation;
28
use think\model\relation\OneToOne;
29
use think\Paginator;
30
31
class Query
1 ignored issue
show
Coding Style introduced by
Missing class doc comment
Loading history...
32
{
33
    /**
34
     * 当前数据库连接对象
35
     * @var Connection
36
     */
37
    protected $connection;
38
39
    /**
40
     * 当前模型对象
41
     * @var Model
42
     */
43
    protected $model;
44
45
    /**
46
     * 当前数据表名称(不含前缀)
47
     * @var string
48
     */
49
    protected $name = '';
50
51
    /**
52
     * 当前数据表主键
53
     * @var string|array
54
     */
55
    protected $pk;
56
57
    /**
58
     * 当前数据表前缀
59
     * @var string
60
     */
61
    protected $prefix = '';
62
63
    /**
64
     * 当前查询参数
65
     * @var array
66
     */
67
    protected $options = [];
68
69
    /**
70
     * 当前参数绑定
71
     * @var array
72
     */
73
    protected $bind = [];
74
75
    /**
76
     * 事件回调
77
     * @var array
78
     */
79
    private static $event = [];
0 ignored issues
show
Coding Style introduced by
Private member variable "event" must be prefixed with an underscore
Loading history...
80
81
    /**
82
     * 扩展查询方法
83
     * @var array
84
     */
85
    private static $extend = [];
0 ignored issues
show
Coding Style introduced by
Private member variable "extend" must be prefixed with an underscore
Loading history...
86
87
    /**
88
     * 读取主库的表
89
     * @var array
90
     */
91
    protected static $readMaster = [];
92
93
    /**
94
     * 日期查询表达式
95
     * @var array
96
     */
97
    protected $timeRule = [
98
        'today'      => ['today', 'tomorrow'],
99
        'yesterday'  => ['yesterday', 'today'],
100
        'week'       => ['this week 00:00:00', 'next week 00:00:00'],
101
        'last week'  => ['last week 00:00:00', 'this week 00:00:00'],
102
        'month'      => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'],
103
        'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'],
104
        'year'       => ['this year 1/1', 'next year 1/1'],
105
        'last year'  => ['last year 1/1', 'this year 1/1'],
106
    ];
107
108
    /**
109
     * 日期查询快捷定义
110
     * @var array
111
     */
112
    protected $timeExp = ['d' => 'today', 'w' => 'week', 'm' => 'month', 'y' => 'year'];
113
114
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $connection should have a doc-comment as per coding-style.
Loading history...
115
     * 架构函数
116
     * @access public
117
     */
118
    public function __construct(Connection $connection = null)
119
    {
120
        if (is_null($connection)) {
121
            $this->connection = Db::connect();
122
        } else {
123
            $this->connection = $connection;
124
        }
125
126
        $this->prefix = $this->connection->getConfig('prefix');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->connection->getConfig('prefix') can also be of type array. However, the property $prefix is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
127
    }
128
129
    /**
130
     * 创建一个新的查询对象
131
     * @access public
132
     * @return Query
133
     */
134
    public function newQuery()
135
    {
136
        return new static($this->connection);
137
    }
138
139
    /**
140
     * 利用__call方法实现一些特殊的Model方法
141
     * @access public
142
     * @param  string $method 方法名称
143
     * @param  array  $args   调用参数
144
     * @return mixed
145
     * @throws DbException
146
     * @throws Exception
147
     */
148
    public function __call($method, $args)
149
    {
150
        if (isset(self::$extend[strtolower($method)])) {
151
            // 调用扩展查询方法
152
            array_unshift($args, $this);
153
154
            return Container::getInstance()
155
                ->invoke(self::$extend[strtolower($method)], $args);
156
        } elseif (strtolower(substr($method, 0, 5)) == 'getby') {
157
            // 根据某个字段获取记录
158
            $field = Loader::parseName(substr($method, 5));
159
            return $this->where($field, '=', $args[0])->find();
160
        } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
161
            // 根据某个字段获取记录的某个值
162
            $name = Loader::parseName(substr($method, 10));
163
            return $this->where($name, '=', $args[0])->value($args[1]);
164
        } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
165
            $name = Loader::parseName(substr($method, 7));
166
            array_unshift($args, $name);
167
            return call_user_func_array([$this, 'whereOr'], $args);
168
        } elseif (strtolower(substr($method, 0, 5)) == 'where') {
169
            $name = Loader::parseName(substr($method, 5));
170
            array_unshift($args, $name);
171
            return call_user_func_array([$this, 'where'], $args);
172
        } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
173
            // 动态调用命名范围
174
            $method = 'scope' . $method;
175
            array_unshift($args, $this);
176
177
            call_user_func_array([$this->model, $method], $args);
178
            return $this;
179
        } else {
180
            throw new Exception('method not exist:' . ($this->model ? get_class($this->model) : static::class) . '->' . $method);
181
        }
182
    }
183
184
    /**
185
     * 扩展查询方法
186
     * @access public
187
     * @param  string|array  $method     查询方法名
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
188
     * @param  callable      $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
189
     * @return void
190
     */
191
    public static function extend($method, $callback = null)
192
    {
193
        if (is_array($method)) {
194
            foreach ($method as $key => $val) {
195
                self::$extend[strtolower($key)] = $val;
196
            }
197
        } else {
198
            self::$extend[strtolower($method)] = $callback;
199
        }
200
    }
201
202
    /**
203
     * 设置当前的数据库Connection对象
204
     * @access public
205
     * @param  Connection      $connection
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
206
     * @return $this
207
     */
208
    public function setConnection(Connection $connection)
209
    {
210
        $this->connection = $connection;
211
        $this->prefix     = $this->connection->getConfig('prefix');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->connection->getConfig('prefix') can also be of type array. However, the property $prefix is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
212
213
        return $this;
214
    }
215
216
    /**
217
     * 获取当前的数据库Connection对象
218
     * @access public
219
     * @return Connection
220
     */
221
    public function getConnection()
222
    {
223
        return $this->connection;
224
    }
225
226
    /**
227
     * 指定模型
228
     * @access public
229
     * @param  Model $model 模型对象实例
230
     * @return $this
231
     */
232
    public function model(Model $model)
233
    {
234
        $this->model = $model;
235
        return $this;
236
    }
237
238
    /**
239
     * 获取当前的模型对象
240
     * @access public
241
     * @return Model|null
242
     */
243
    public function getModel()
244
    {
245
        return $this->model ? $this->model->setQuery($this) : null;
246
    }
247
248
    /**
249
     * 设置从主库读取数据
250
     * @access public
251
     * @param  bool $all 是否所有表有效
252
     * @return $this
253
     */
254
    public function readMaster($all = false)
255
    {
256
        $table = $all ? '*' : $this->getTable();
257
258
        static::$readMaster[$table] = true;
259
260
        return $this;
261
    }
262
263
    /**
264
     * 指定当前数据表名(不含前缀)
265
     * @access public
266
     * @param  string $name
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
267
     * @return $this
268
     */
269
    public function name($name)
270
    {
271
        $this->name = $name;
272
        return $this;
273
    }
274
275
    /**
276
     * 获取当前的数据表名称
277
     * @access public
278
     * @return string
279
     */
280
    public function getName()
281
    {
282
        return $this->name ?: $this->model->getName();
283
    }
284
285
    /**
286
     * 得到当前或者指定名称的数据表
287
     * @access public
288
     * @param  string $name
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
289
     * @return string
290
     */
291
    public function getTable($name = '')
292
    {
293
        if (empty($name) && isset($this->options['table'])) {
294
            return $this->options['table'];
295
        }
296
297
        $name = $name ?: $this->name;
298
299
        return $this->prefix . Loader::parseName($name);
300
    }
301
302
    /**
303
     * 执行查询 返回数据集
304
     * @access public
305
     * @param  string      $sql    sql指令
306
     * @param  array       $bind   参数绑定
307
     * @param  boolean     $master 是否在主服务器读操作
308
     * @param  bool        $pdo    是否返回PDO对象
309
     * @return mixed
310
     * @throws BindParamException
311
     * @throws PDOException
312
     */
313
    public function query($sql, $bind = [], $master = false, $pdo = false)
314
    {
315
        return $this->connection->query($sql, $bind, $master, $pdo);
316
    }
317
318
    /**
319
     * 执行语句
320
     * @access public
321
     * @param  string $sql  sql指令
322
     * @param  array  $bind 参数绑定
323
     * @return int
324
     * @throws BindParamException
325
     * @throws PDOException
326
     */
327
    public function execute($sql, $bind = [])
328
    {
329
        return $this->connection->execute($sql, $bind, $this);
330
    }
331
332
    /**
333
     * 监听SQL执行
334
     * @access public
335
     * @param  callable $callback 回调方法
336
     * @return void
337
     */
338
    public function listen($callback)
339
    {
340
        $this->connection->listen($callback);
341
    }
342
343
    /**
344
     * 获取最近插入的ID
345
     * @access public
346
     * @param  string $sequence 自增序列名
347
     * @return string
348
     */
349
    public function getLastInsID($sequence = null)
350
    {
351
        return $this->connection->getLastInsID($sequence);
352
    }
353
354
    /**
355
     * 获取返回或者影响的记录数
356
     * @access public
357
     * @return integer
358
     */
359
    public function getNumRows()
360
    {
361
        return $this->connection->getNumRows();
362
    }
363
364
    /**
365
     * 获取最近一次查询的sql语句
366
     * @access public
367
     * @return string
368
     */
369
    public function getLastSql()
370
    {
371
        return $this->connection->getLastSql();
372
    }
373
374
    /**
375
     * 执行数据库Xa事务
376
     * @access public
377
     * @param  callable $callback 数据操作方法回调
378
     * @param  array    $dbs      多个查询对象或者连接对象
379
     * @return mixed
380
     * @throws PDOException
381
     * @throws \Exception
382
     * @throws \Throwable
383
     */
384
    public function transactionXa($callback, array $dbs = [])
385
    {
386
        $xid = uniqid('xa');
387
388
        if (empty($dbs)) {
389
            $dbs[] = $this->getConnection();
390
        }
391
392
        foreach ($dbs as $key => $db) {
393
            if ($db instanceof Query) {
394
                $db = $db->getConnection();
395
396
                $dbs[$key] = $db;
397
            }
398
399
            $db->startTransXa($xid);
400
        }
401
402
        try {
403
            $result = null;
404
            if (is_callable($callback)) {
405
                $result = call_user_func_array($callback, [$this]);
406
            }
407
408
            foreach ($dbs as $db) {
409
                $db->prepareXa($xid);
410
            }
411
412
            foreach ($dbs as $db) {
413
                $db->commitXa($xid);
414
            }
415
416
            return $result;
417
        } catch (\Exception $e) {
418
            foreach ($dbs as $db) {
419
                $db->rollbackXa($xid);
420
            }
421
            throw $e;
422
        } catch (\Throwable $e) {
423
            foreach ($dbs as $db) {
424
                $db->rollbackXa($xid);
425
            }
426
            throw $e;
427
        }
428
    }
429
430
    /**
431
     * 执行数据库事务
432
     * @access public
433
     * @param  callable $callback 数据操作方法回调
434
     * @return mixed
435
     */
436
    public function transaction($callback)
437
    {
438
        return $this->connection->transaction($callback);
439
    }
440
441
    /**
442
     * 启动事务
443
     * @access public
444
     * @return void
445
     */
446
    public function startTrans()
447
    {
448
        $this->connection->startTrans();
449
    }
450
451
    /**
452
     * 用于非自动提交状态下面的查询提交
453
     * @access public
454
     * @return void
455
     * @throws PDOException
456
     */
457
    public function commit()
458
    {
459
        $this->connection->commit();
460
    }
461
462
    /**
463
     * 事务回滚
464
     * @access public
465
     * @return void
466
     * @throws PDOException
467
     */
468
    public function rollback()
469
    {
470
        $this->connection->rollback();
471
    }
472
473
    /**
474
     * 批处理执行SQL语句
475
     * 批处理的指令都认为是execute操作
476
     * @access public
477
     * @param  array $sql SQL批处理指令
478
     * @return boolean
479
     */
480
    public function batchQuery($sql = [])
481
    {
482
        return $this->connection->batchQuery($sql);
483
    }
484
485
    /**
486
     * 获取数据库的配置参数
487
     * @access public
488
     * @param  string $name 参数名称
489
     * @return mixed
490
     */
491
    public function getConfig($name = '')
492
    {
493
        return $this->connection->getConfig($name);
494
    }
495
496
    /**
497
     * 获取数据表字段信息
498
     * @access public
499
     * @param  string $tableName 数据表名
500
     * @return array
501
     */
502
    public function getTableFields($tableName = '')
503
    {
504
        if ('' == $tableName) {
505
            $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable();
506
        }
507
508
        return $this->connection->getTableFields($tableName);
509
    }
510
511
    /**
512
     * 获取数据表字段类型
513
     * @access public
514
     * @param  string $tableName 数据表名
515
     * @param  string $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 4 found
Loading history...
516
     * @return array|string
517
     */
518
    public function getFieldsType($tableName = '', $field = null)
519
    {
520
        if ('' == $tableName) {
521
            $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable();
522
        }
523
524
        return $this->connection->getFieldsType($tableName, $field);
525
    }
526
527
    /**
528
     * 得到分表的的数据表名
529
     * @access public
530
     * @param  array  $data  操作的数据
531
     * @param  string $field 分表依据的字段
532
     * @param  array  $rule  分表规则
533
     * @return array
534
     */
535
    public function getPartitionTableName($data, $field, $rule = [])
536
    {
537
        // 对数据表进行分区
538
        if ($field && isset($data[$field])) {
539
            $value = $data[$field];
540
            $type  = $rule['type'];
541
            switch ($type) {
542
                case 'id':
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
543
                    // 按照id范围分表
544
                    $step = $rule['expr'];
545
                    $seq  = floor($value / $step) + 1;
546
                    break;
547
                case 'year':
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
548
                    // 按照年份分表
549
                    if (!is_numeric($value)) {
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
550
                        $value = strtotime($value);
551
                    }
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
552
                    $seq = date('Y', $value) - $rule['expr'] + 1;
553
                    break;
554
                case 'mod':
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
555
                    // 按照id的模数分表
556
                    $seq = ($value % $rule['num']) + 1;
557
                    break;
558
                case 'md5':
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
559
                    // 按照md5的序列分表
560
                    $seq = (ord(substr(md5($value), 0, 1)) % $rule['num']) + 1;
561
                    break;
562
                default:
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
563
                    if (function_exists($type)) {
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
564
                        // 支持指定函数哈希
565
                        $seq = (ord(substr($type($value), 0, 1)) % $rule['num']) + 1;
566
                    } else {
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
567
                        // 按照字段的首字母的值分表
568
                        $seq = (ord($value{0}) % $rule['num']) + 1;
569
                    }
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
570
            }
571
            return $this->getTable() . '_' . $seq;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getTable() . '_' . $seq returns the type string which is incompatible with the documented return type array.
Loading history...
572
        }
573
        // 当设置的分表字段不在查询条件或者数据中
574
        // 进行联合查询,必须设定 partition['num']
575
        $tableName = [];
576
        for ($i = 0; $i < $rule['num']; $i++) {
577
            $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1);
578
        }
579
580
        return ['( ' . implode(" UNION ", $tableName) . ' )' => $this->name];
581
    }
582
583
    /**
584
     * 得到某个字段的值
585
     * @access public
586
     * @param  string $field   字段名
587
     * @param  mixed  $default 默认值
588
     * @return mixed
589
     */
590
    public function value($field, $default = null)
591
    {
592
        $this->parseOptions();
593
594
        return $this->connection->value($this, $field, $default);
595
    }
596
597
    /**
598
     * 得到某个列的数组
599
     * @access public
600
     * @param  string $field 字段名 多个字段用逗号分隔
601
     * @param  string $key   索引
602
     * @return array
603
     */
604
    public function column($field, $key = '')
605
    {
606
        $this->parseOptions();
607
608
        return $this->connection->column($this, $field, $key);
609
    }
610
611
    /**
612
     * 聚合查询
613
     * @access public
614
     * @param  string               $aggregate    聚合方法
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
615
     * @param  string|Expression    $field        字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
616
     * @param  bool                 $force        强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 8 found
Loading history...
617
     * @return mixed
618
     */
619
    public function aggregate($aggregate, $field, $force = false)
620
    {
621
        $this->parseOptions();
622
623
        $result = $this->connection->aggregate($this, $aggregate, $field);
624
625
        if (!empty($this->options['fetch_sql'])) {
626
            return $result;
627
        } elseif ($force) {
628
            $result = (float) $result;
629
        }
630
631
        return $result;
632
    }
633
634
    /**
635
     * COUNT查询
636
     * @access public
637
     * @param  string|Expression $field 字段名
638
     * @return float|string
639
     */
640
    public function count($field = '*')
641
    {
642
        if (!empty($this->options['group'])) {
643
            // 支持GROUP
644
            $options = $this->getOptions();
645
            $subSql  = $this->options($options)
646
                ->field('count(' . $field . ') AS think_count')
647
                ->bind($this->bind)
648
                ->buildSql();
649
650
            $query = $this->newQuery()->table([$subSql => '_group_count_']);
651
652
            if (!empty($options['fetch_sql'])) {
653
                $query->fetchSql(true);
654
            }
655
656
            $count = $query->aggregate('COUNT', '*', true);
657
        } else {
658
            $count = $this->aggregate('COUNT', $field, true);
659
        }
660
661
        return is_string($count) ? $count : (int) $count;
662
    }
663
664
    /**
665
     * SUM查询
666
     * @access public
667
     * @param  string|Expression $field 字段名
668
     * @return float
669
     */
670
    public function sum($field)
671
    {
672
        return $this->aggregate('SUM', $field, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->aggregate('SUM', $field, true) also could return the type string which is incompatible with the documented return type double.
Loading history...
673
    }
674
675
    /**
676
     * MIN查询
677
     * @access public
678
     * @param  string|Expression $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
679
     * @param  bool              $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
680
     * @return mixed
681
     */
682
    public function min($field, $force = true)
683
    {
684
        return $this->aggregate('MIN', $field, $force);
685
    }
686
687
    /**
688
     * MAX查询
689
     * @access public
690
     * @param  string|Expression $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
691
     * @param  bool              $force    强制转为数字类型
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
692
     * @return mixed
693
     */
694
    public function max($field, $force = true)
695
    {
696
        return $this->aggregate('MAX', $field, $force);
697
    }
698
699
    /**
700
     * AVG查询
701
     * @access public
702
     * @param  string|Expression $field 字段名
703
     * @return float
704
     */
705
    public function avg($field)
706
    {
707
        return $this->aggregate('AVG', $field, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->aggregate('AVG', $field, true) also could return the type string which is incompatible with the documented return type double.
Loading history...
708
    }
709
710
    /**
711
     * 设置记录的某个字段值
712
     * 支持使用数据库字段和方法
713
     * @access public
714
     * @param  string|array $field 字段名
715
     * @param  mixed        $value 字段值
716
     * @return integer
717
     */
718
    public function setField($field, $value = '')
719
    {
720
        if (is_array($field)) {
721
            $data = $field;
722
        } else {
723
            $data[$field] = $value;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.
Loading history...
724
        }
725
726
        return $this->update($data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->update($data) also could return the type string which is incompatible with the documented return type integer.
Loading history...
727
    }
728
729
    /**
730
     * 字段值(延迟)增长
731
     * @access public
732
     * @param  string  $field    字段名
733
     * @param  integer $step     增长值
734
     * @param  integer $lazyTime 延时时间(s)
735
     * @return integer|true
736
     * @throws Exception
737
     */
738
    public function setInc($field, $step = 1, $lazyTime = 0)
739
    {
740
        $condition = !empty($this->options['where']) ? $this->options['where'] : [];
741
742
        if (empty($condition)) {
743
            // 没有条件不做任何更新
744
            throw new Exception('no data to update');
745
        }
746
747
        if ($lazyTime > 0) {
748
            // 延迟写入
749
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
750
            $step = $this->lazyWrite('inc', $guid, $step, $lazyTime);
751
752
            if (false === $step) {
753
                // 清空查询条件
754
                $this->options = [];
755
                return true;
756
            }
757
        }
758
759
        return $this->setField($field, ['INC', $step]);
760
    }
761
762
    /**
763
     * 字段值(延迟)减少
764
     * @access public
765
     * @param  string  $field    字段名
766
     * @param  integer $step     减少值
767
     * @param  integer $lazyTime 延时时间(s)
768
     * @return integer|true
769
     * @throws Exception
770
     */
771
    public function setDec($field, $step = 1, $lazyTime = 0)
772
    {
773
        $condition = !empty($this->options['where']) ? $this->options['where'] : [];
774
775
        if (empty($condition)) {
776
            // 没有条件不做任何更新
777
            throw new Exception('no data to update');
778
        }
779
780
        if ($lazyTime > 0) {
781
            // 延迟写入
782
            $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition));
783
            $step = $this->lazyWrite('dec', $guid, $step, $lazyTime);
784
785
            if (false === $step) {
786
                // 清空查询条件
787
                $this->options = [];
788
                return true;
789
            }
790
791
            $value = ['INC', $step];
792
        } else {
793
            $value = ['DEC', $step];
794
        }
795
796
        return $this->setField($field, $value);
797
    }
798
799
    /**
800
     * 延时更新检查 返回false表示需要延时
801
     * 否则返回实际写入的数值
802
     * @access protected
803
     * @param  string  $type     自增或者自减
804
     * @param  string  $guid     写入标识
805
     * @param  integer $step     写入步进值
806
     * @param  integer $lazyTime 延时时间(s)
807
     * @return false|integer
808
     */
809
    protected function lazyWrite($type, $guid, $step, $lazyTime)
810
    {
811
        $cache = Container::get('cache');
812
813
        if (!$cache->has($guid . '_time')) {
814
            // 计时开始
815
            $cache->set($guid . '_time', time(), 0);
816
            $cache->$type($guid, $step);
817
        } elseif (time() > $cache->get($guid . '_time') + $lazyTime) {
818
            // 删除缓存
819
            $value = $cache->$type($guid, $step);
820
            $cache->rm($guid);
821
            $cache->rm($guid . '_time');
822
            return 0 === $value ? false : $value;
823
        } else {
824
            // 更新缓存
825
            $cache->$type($guid, $step);
826
        }
827
828
        return false;
829
    }
830
831
    /**
832
     * 查询SQL组装 join
833
     * @access public
834
     * @param  mixed  $join      关联的表名
835
     * @param  mixed  $condition 条件
836
     * @param  string $type      JOIN类型
837
     * @param  array  $bind      参数绑定
838
     * @return $this
839
     */
840
    public function join($join, $condition = null, $type = 'INNER', $bind = [])
841
    {
842
        if (empty($condition)) {
843
            // 如果为组数,则循环调用join
844
            foreach ($join as $key => $value) {
845
                if (is_array($value) && 2 <= count($value)) {
846
                    $this->join($value[0], $value[1], isset($value[2]) ? $value[2] : $type);
847
                }
848
            }
849
        } else {
850
            $table = $this->getJoinTable($join);
851
            if ($bind) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bind of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
852
                $this->bindParams($condition, $bind);
853
            }
854
            $this->options['join'][] = [$table, strtoupper($type), $condition];
855
        }
856
857
        return $this;
858
    }
859
860
    /**
861
     * LEFT JOIN
862
     * @access public
863
     * @param  mixed  $join      关联的表名
864
     * @param  mixed  $condition 条件
865
     * @param  array  $bind      参数绑定
866
     * @return $this
867
     */
868
    public function leftJoin($join, $condition = null, $bind = [])
869
    {
870
        return $this->join($join, $condition, 'LEFT');
871
    }
872
873
    /**
874
     * RIGHT JOIN
875
     * @access public
876
     * @param  mixed  $join      关联的表名
877
     * @param  mixed  $condition 条件
878
     * @param  array  $bind      参数绑定
879
     * @return $this
880
     */
881
    public function rightJoin($join, $condition = null, $bind = [])
882
    {
883
        return $this->join($join, $condition, 'RIGHT');
884
    }
885
886
    /**
887
     * FULL JOIN
888
     * @access public
889
     * @param  mixed  $join      关联的表名
890
     * @param  mixed  $condition 条件
891
     * @param  array  $bind      参数绑定
892
     * @return $this
893
     */
894
    public function fullJoin($join, $condition = null, $bind = [])
895
    {
896
        return $this->join($join, $condition, 'FULL');
897
    }
898
899
    /**
900
     * 获取Join表名及别名 支持
901
     * ['prefix_table或者子查询'=>'alias'] 'table alias'
902
     * @access protected
903
     * @param  array|string $join
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
904
     * @param  string       $alias
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
905
     * @return string
906
     */
907
    protected function getJoinTable($join, &$alias = null)
908
    {
909
        if (is_array($join)) {
910
            $table = $join;
911
            $alias = array_shift($join);
912
        } else {
913
            $join = trim($join);
914
915
            if (false !== strpos($join, '(')) {
916
                // 使用子查询
917
                $table = $join;
918
            } else {
919
                $prefix = $this->prefix;
920
                if (strpos($join, ' ')) {
921
                    // 使用别名
922
                    list($table, $alias) = explode(' ', $join);
923
                } else {
924
                    $table = $join;
925
                    if (false === strpos($join, '.') && 0 !== strpos($join, '__')) {
926
                        $alias = $join;
927
                    }
928
                }
929
930
                if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) {
931
                    $table = $this->getTable($table);
932
                }
933
            }
934
935
            if (isset($alias) && $table != $alias) {
936
                $table = [$table => $alias];
937
            }
938
        }
939
940
        return $table;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $table also could return the type array|array<string,string> which is incompatible with the documented return type string.
Loading history...
941
    }
942
943
    /**
944
     * 查询SQL组装 union
945
     * @access public
946
     * @param  mixed   $union
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
947
     * @param  boolean $all
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
948
     * @return $this
949
     */
950
    public function union($union, $all = false)
951
    {
952
        if (empty($union)) {
953
            return $this;
954
        }
955
956
        $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
957
958
        if (is_array($union)) {
959
            $this->options['union'] = array_merge($this->options['union'], $union);
960
        } else {
961
            $this->options['union'][] = $union;
962
        }
963
964
        return $this;
965
    }
966
967
    /**
968
     * 查询SQL组装 union all
969
     * @access public
970
     * @param  mixed   $union
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
971
     * @return $this
972
     */
973
    public function unionAll($union)
974
    {
975
        return $this->union($union, true);
976
    }
977
978
    /**
979
     * 指定查询字段 支持字段排除和指定数据表
980
     * @access public
981
     * @param  mixed   $field
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
982
     * @param  boolean $except    是否排除
983
     * @param  string  $tableName 数据表名
984
     * @param  string  $prefix    字段前缀
985
     * @param  string  $alias     别名前缀
986
     * @return $this
987
     */
988
    public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '')
989
    {
990
        if (empty($field)) {
991
            return $this;
992
        } elseif ($field instanceof Expression) {
993
            $this->options['field'][] = $field;
994
            return $this;
995
        }
996
997
        if (is_string($field)) {
998
            if (preg_match('/[\<\'\"\(]/', $field)) {
999
                return $this->fieldRaw($field);
1000
            }
1001
1002
            $field = array_map('trim', explode(',', $field));
1003
        }
1004
1005
        if (true === $field) {
1006
            // 获取全部字段
1007
            $fields = $this->getTableFields($tableName);
1008
            $field  = $fields ?: ['*'];
1009
        } elseif ($except) {
1010
            // 字段排除
1011
            $fields = $this->getTableFields($tableName);
1012
            $field  = $fields ? array_diff($fields, $field) : $field;
1013
        }
1014
1015
        if ($tableName) {
1016
            // 添加统一的前缀
1017
            $prefix = $prefix ?: $tableName;
1018
            foreach ($field as $key => &$val) {
1019
                if (is_numeric($key) && $alias) {
1020
                    $field[$prefix . '.' . $val] = $alias . $val;
1021
                    unset($field[$key]);
1022
                } elseif (is_numeric($key)) {
1023
                    $val = $prefix . '.' . $val;
1024
                }
1025
            }
1026
        }
1027
1028
        if (isset($this->options['field'])) {
1029
            $field = array_merge((array) $this->options['field'], $field);
1030
        }
1031
1032
        $this->options['field'] = array_unique($field);
1033
1034
        return $this;
1035
    }
1036
1037
    /**
1038
     * 表达式方式指定查询字段
1039
     * @access public
1040
     * @param  string $field    字段名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
1041
     * @return $this
1042
     */
1043
    public function fieldRaw($field)
1044
    {
1045
        $this->options['field'][] = $this->raw($field);
1046
1047
        return $this;
1048
    }
1049
1050
    /**
1051
     * 设置数据
1052
     * @access public
1053
     * @param  mixed $field 字段名或者数据
1054
     * @param  mixed $value 字段值
1055
     * @return $this
1056
     */
1057
    public function data($field, $value = null)
1058
    {
1059
        if (is_array($field)) {
1060
            $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field;
1061
        } else {
1062
            $this->options['data'][$field] = $value;
1063
        }
1064
1065
        return $this;
1066
    }
1067
1068
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $op should have a doc-comment as per coding-style.
Loading history...
1069
     * 字段值增长
1070
     * @access public
1071
     * @param  string|array $field 字段名
1072
     * @param  integer      $step  增长值
1073
     * @return $this
1074
     */
1075
    public function inc($field, $step = 1, $op = 'INC')
1076
    {
1077
        $fields = is_string($field) ? explode(',', $field) : $field;
1078
1079
        foreach ($fields as $field => $val) {
0 ignored issues
show
introduced by
$field is overwriting one of the parameters of this function.
Loading history...
1080
            if (is_numeric($field)) {
1081
                $field = $val;
1082
            } else {
1083
                $step = $val;
1084
            }
1085
1086
            $this->data($field, [$op, $step]);
1087
        }
1088
1089
        return $this;
1090
    }
1091
1092
    /**
1093
     * 字段值减少
1094
     * @access public
1095
     * @param  string|array $field 字段名
1096
     * @param  integer      $step  增长值
1097
     * @return $this
1098
     */
1099
    public function dec($field, $step = 1)
1100
    {
1101
        return $this->inc($field, $step, 'DEC');
1102
    }
1103
1104
    /**
1105
     * 使用表达式设置数据
1106
     * @access public
1107
     * @param  string $field 字段名
1108
     * @param  string $value 字段值
1109
     * @return $this
1110
     */
1111
    public function exp($field, $value)
1112
    {
1113
        $this->data($field, $this->raw($value));
1114
        return $this;
1115
    }
1116
1117
    /**
1118
     * 使用表达式设置数据
1119
     * @access public
1120
     * @param  mixed $value 表达式
1121
     * @return Expression
1122
     */
1123
    public function raw($value)
1124
    {
1125
        return new Expression($value);
1126
    }
1127
1128
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $join should have a doc-comment as per coding-style.
Loading history...
1129
     * 指定JOIN查询字段
1130
     * @access public
1131
     * @param  string|array $table 数据表
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter $table does not match actual variable name $join
Loading history...
1132
     * @param  string|array $field 查询字段
1133
     * @param  mixed        $on    JOIN条件
1134
     * @param  string       $type  JOIN类型
1135
     * @return $this
1136
     */
1137
    public function view($join, $field = true, $on = null, $type = 'INNER')
1138
    {
1139
        $this->options['view'] = true;
1140
1141
        if (is_array($join) && key($join) === 0) {
1142
            foreach ($join as $key => $val) {
1143
                $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER');
1144
            }
1145
        } else {
1146
            $fields = [];
1147
            $table  = $this->getJoinTable($join, $alias);
1148
1149
            if (true === $field) {
1150
                $fields = $alias . '.*';
1151
            } else {
1152
                if (is_string($field)) {
1153
                    $field = explode(',', $field);
1154
                }
1155
1156
                foreach ($field as $key => $val) {
1157
                    if (is_numeric($key)) {
1158
                        $fields[] = $alias . '.' . $val;
1159
1160
                        $this->options['map'][$val] = $alias . '.' . $val;
1161
                    } else {
1162
                        if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
1163
                            $name = $key;
1164
                        } else {
1165
                            $name = $alias . '.' . $key;
1166
                        }
1167
1168
                        $fields[] = $name . ' AS ' . $val;
1169
1170
                        $this->options['map'][$val] = $name;
1171
                    }
1172
                }
1173
            }
1174
1175
            $this->field($fields);
1176
1177
            if ($on) {
1178
                $this->join($table, $on, $type);
1179
            } else {
1180
                $this->table($table);
1181
            }
1182
        }
1183
1184
        return $this;
1185
    }
1186
1187
    /**
1188
     * 设置分表规则
1189
     * @access public
1190
     * @param  array  $data  操作的数据
1191
     * @param  string $field 分表依据的字段
1192
     * @param  array  $rule  分表规则
1193
     * @return $this
1194
     */
1195
    public function partition($data, $field, $rule = [])
1196
    {
1197
        $this->options['table'] = $this->getPartitionTableName($data, $field, $rule);
1198
1199
        return $this;
1200
    }
1201
1202
    /**
1203
     * 指定AND查询条件
1204
     * @access public
1205
     * @param  mixed $field     查询字段
1206
     * @param  mixed $op        查询表达式
1207
     * @param  mixed $condition 查询条件
1208
     * @return $this
1209
     */
1210
    public function where($field, $op = null, $condition = null)
1211
    {
1212
        $param = func_get_args();
1213
        array_shift($param);
1214
        return $this->parseWhereExp('AND', $field, $op, $condition, $param);
1215
    }
1216
1217
    /**
1218
     * 指定OR查询条件
1219
     * @access public
1220
     * @param  mixed $field     查询字段
1221
     * @param  mixed $op        查询表达式
1222
     * @param  mixed $condition 查询条件
1223
     * @return $this
1224
     */
1225
    public function whereOr($field, $op = null, $condition = null)
1226
    {
1227
        $param = func_get_args();
1228
        array_shift($param);
1229
        return $this->parseWhereExp('OR', $field, $op, $condition, $param);
1230
    }
1231
1232
    /**
1233
     * 指定XOR查询条件
1234
     * @access public
1235
     * @param  mixed $field     查询字段
1236
     * @param  mixed $op        查询表达式
1237
     * @param  mixed $condition 查询条件
1238
     * @return $this
1239
     */
1240
    public function whereXor($field, $op = null, $condition = null)
1241
    {
1242
        $param = func_get_args();
1243
        array_shift($param);
1244
        return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
1245
    }
1246
1247
    /**
1248
     * 指定Null查询条件
1249
     * @access public
1250
     * @param  mixed  $field 查询字段
1251
     * @param  string $logic 查询逻辑 and or xor
1252
     * @return $this
1253
     */
1254
    public function whereNull($field, $logic = 'AND')
1255
    {
1256
        return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
1257
    }
1258
1259
    /**
1260
     * 指定NotNull查询条件
1261
     * @access public
1262
     * @param  mixed  $field 查询字段
1263
     * @param  string $logic 查询逻辑 and or xor
1264
     * @return $this
1265
     */
1266
    public function whereNotNull($field, $logic = 'AND')
1267
    {
1268
        return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
1269
    }
1270
1271
    /**
1272
     * 指定Exists查询条件
1273
     * @access public
1274
     * @param  mixed  $condition 查询条件
1275
     * @param  string $logic     查询逻辑 and or xor
1276
     * @return $this
1277
     */
1278
    public function whereExists($condition, $logic = 'AND')
1279
    {
1280
        if (is_string($condition)) {
1281
            $condition = $this->raw($condition);
1282
        }
1283
1284
        $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
1285
        return $this;
1286
    }
1287
1288
    /**
1289
     * 指定NotExists查询条件
1290
     * @access public
1291
     * @param  mixed  $condition 查询条件
1292
     * @param  string $logic     查询逻辑 and or xor
1293
     * @return $this
1294
     */
1295
    public function whereNotExists($condition, $logic = 'AND')
1296
    {
1297
        if (is_string($condition)) {
1298
            $condition = $this->raw($condition);
1299
        }
1300
1301
        $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
1302
        return $this;
1303
    }
1304
1305
    /**
1306
     * 指定In查询条件
1307
     * @access public
1308
     * @param  mixed  $field     查询字段
1309
     * @param  mixed  $condition 查询条件
1310
     * @param  string $logic     查询逻辑 and or xor
1311
     * @return $this
1312
     */
1313
    public function whereIn($field, $condition, $logic = 'AND')
1314
    {
1315
        return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
1316
    }
1317
1318
    /**
1319
     * 指定NotIn查询条件
1320
     * @access public
1321
     * @param  mixed  $field     查询字段
1322
     * @param  mixed  $condition 查询条件
1323
     * @param  string $logic     查询逻辑 and or xor
1324
     * @return $this
1325
     */
1326
    public function whereNotIn($field, $condition, $logic = 'AND')
1327
    {
1328
        return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
1329
    }
1330
1331
    /**
1332
     * 指定Like查询条件
1333
     * @access public
1334
     * @param  mixed  $field     查询字段
1335
     * @param  mixed  $condition 查询条件
1336
     * @param  string $logic     查询逻辑 and or xor
1337
     * @return $this
1338
     */
1339
    public function whereLike($field, $condition, $logic = 'AND')
1340
    {
1341
        return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
1342
    }
1343
1344
    /**
1345
     * 指定NotLike查询条件
1346
     * @access public
1347
     * @param  mixed  $field     查询字段
1348
     * @param  mixed  $condition 查询条件
1349
     * @param  string $logic     查询逻辑 and or xor
1350
     * @return $this
1351
     */
1352
    public function whereNotLike($field, $condition, $logic = 'AND')
1353
    {
1354
        return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
1355
    }
1356
1357
    /**
1358
     * 指定Between查询条件
1359
     * @access public
1360
     * @param  mixed  $field     查询字段
1361
     * @param  mixed  $condition 查询条件
1362
     * @param  string $logic     查询逻辑 and or xor
1363
     * @return $this
1364
     */
1365
    public function whereBetween($field, $condition, $logic = 'AND')
1366
    {
1367
        return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
1368
    }
1369
1370
    /**
1371
     * 指定NotBetween查询条件
1372
     * @access public
1373
     * @param  mixed  $field     查询字段
1374
     * @param  mixed  $condition 查询条件
1375
     * @param  string $logic     查询逻辑 and or xor
1376
     * @return $this
1377
     */
1378
    public function whereNotBetween($field, $condition, $logic = 'AND')
1379
    {
1380
        return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
1381
    }
1382
1383
    /**
1384
     * 比较两个字段
1385
     * @access public
1386
     * @param  string|array $field1     查询字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1387
     * @param  string       $operator   比较操作符
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
1388
     * @param  string       $field2     比较字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
1389
     * @param  string       $logic      查询逻辑 and or xor
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 6 found
Loading history...
1390
     * @return $this
1391
     */
1392
    public function whereColumn($field1, $operator = null, $field2 = null, $logic = 'AND')
1393
    {
1394
        if (is_array($field1)) {
1395
            foreach ($field1 as $item) {
1396
                $this->whereColumn($item[0], $item[1], isset($item[2]) ? $item[2] : null);
1397
            }
1398
            return $this;
1399
        }
1400
1401
        if (is_null($field2)) {
1402
            $field2   = $operator;
1403
            $operator = '=';
1404
        }
1405
1406
        return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
1407
    }
1408
1409
    /**
1410
     * 设置软删除字段及条件
1411
     * @access public
1412
     * @param  false|string  $field     查询字段
1413
     * @param  mixed         $condition 查询条件
1414
     * @return $this
1415
     */
1416
    public function useSoftDelete($field, $condition = null)
1417
    {
1418
        if ($field) {
1419
            $this->options['soft_delete'] = [$field, $condition];
1420
        }
1421
1422
        return $this;
1423
    }
1424
1425
    /**
1426
     * 指定Exp查询条件
1427
     * @access public
1428
     * @param  mixed  $field     查询字段
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
1429
     * @param  string $where     查询条件
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
1430
     * @param  array  $bind      参数绑定
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 6 found
Loading history...
1431
     * @param  string $logic     查询逻辑 and or xor
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
1432
     * @return $this
1433
     */
1434
    public function whereExp($field, $where, $bind = [], $logic = 'AND')
1435
    {
1436
        if ($bind) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bind of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1437
            $this->bindParams($where, $bind);
1438
        }
1439
1440
        $this->options['where'][$logic][] = [$field, 'EXP', $this->raw($where)];
1441
1442
        return $this;
1443
    }
1444
1445
    /**
1446
     * 指定表达式查询条件
1447
     * @access public
1448
     * @param  string $where  查询条件
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1449
     * @param  array  $bind   参数绑定
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 3 found
Loading history...
1450
     * @param  string $logic  查询逻辑 and or xor
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1451
     * @return $this
1452
     */
1453
    public function whereRaw($where, $bind = [], $logic = 'AND')
1454
    {
1455
        if ($bind) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bind of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1456
            $this->bindParams($where, $bind);
1457
        }
1458
1459
        $this->options['where'][$logic][] = $this->raw($where);
1460
1461
        return $this;
1462
    }
1463
1464
    /**
1465
     * 参数绑定
1466
     * @access public
1467
     * @param  string $sql    绑定的sql表达式
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 4 found
Loading history...
1468
     * @param  array  $bind   参数绑定
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
1469
     * @return void
1470
     */
1471
    protected function bindParams(&$sql, array $bind = [])
1472
    {
1473
        foreach ($bind as $key => $value) {
1474
            if (is_array($value)) {
1475
                $name = $this->bind($value[0], $value[1], isset($value[2]) ? $value[2] : null);
1476
            } else {
1477
                $name = $this->bind($value);
1478
            }
1479
1480
            if (is_numeric($key)) {
1481
                $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
0 ignored issues
show
Bug introduced by
Are you sure $name of type string|think\db\Query can be used in concatenation? ( Ignorable by Annotation )

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

1481
                $sql = substr_replace($sql, ':' . /** @scrutinizer ignore-type */ $name, strpos($sql, '?'), 1);
Loading history...
1482
            } else {
1483
                $sql = str_replace(':' . $key, ':' . $name, $sql);
1484
            }
1485
        }
1486
    }
1487
1488
    /**
1489
     * 指定表达式查询条件 OR
1490
     * @access public
1491
     * @param  string $where  查询条件
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1492
     * @param  array  $bind   参数绑定
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 3 found
Loading history...
1493
     * @return $this
1494
     */
1495
    public function whereOrRaw($where, $bind = [])
1496
    {
1497
        return $this->whereRaw($where, $bind, 'OR');
1498
    }
1499
1500
    /**
1501
     * 分析查询表达式
1502
     * @access protected
1503
     * @param  string   $logic     查询逻辑 and or xor
1504
     * @param  mixed    $field     查询字段
1505
     * @param  mixed    $op        查询表达式
1506
     * @param  mixed    $condition 查询条件
1507
     * @param  array    $param     查询参数
1508
     * @param  bool     $strict    严格模式
1509
     * @return $this
1510
     */
1511
    protected function parseWhereExp($logic, $field, $op, $condition, array $param = [], $strict = false)
1512
    {
1513
        if ($field instanceof $this) {
1514
            $this->options['where'] = $field->getOptions('where');
1515
            return $this;
1516
        }
1517
1518
        $logic = strtoupper($logic);
1519
1520
        if ($field instanceof Where) {
1521
            $this->options['where'][$logic] = $field->parse();
1522
            return $this;
1523
        }
1524
1525
        if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) {
1526
            $field = $this->options['via'] . '.' . $field;
1527
        }
1528
1529
        if ($field instanceof Expression) {
1530
            return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
1531
        } elseif ($strict) {
1532
            // 使用严格模式查询
1533
            $where = [$field, $op, $condition, $logic];
1534
        } elseif (is_array($field)) {
1535
            // 解析数组批量查询
1536
            return $this->parseArrayWhereItems($field, $logic);
1537
        } elseif ($field instanceof \Closure) {
1538
            $where = $field;
1539
        } elseif (is_string($field)) {
1540
            if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
1541
                return $this->whereRaw($field, $op, $logic);
1542
            } elseif (is_string($op) && strtolower($op) == 'exp') {
1543
                $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null;
1544
                return $this->whereExp($field, $condition, $bind, $logic);
1545
            }
1546
1547
            $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
1548
        }
1549
1550
        if (!empty($where)) {
1551
            $this->options['where'][$logic][] = $where;
1552
        }
1553
1554
        return $this;
1555
    }
1556
1557
    /**
1558
     * 分析查询表达式
1559
     * @access protected
1560
     * @param  string   $logic     查询逻辑 and or xor
1561
     * @param  mixed    $field     查询字段
1562
     * @param  mixed    $op        查询表达式
1563
     * @param  mixed    $condition 查询条件
1564
     * @param  array    $param     查询参数
1565
     * @return mixed
1566
     */
1567
    protected function parseWhereItem($logic, $field, $op, $condition, $param = [])
1568
    {
1569
        if (is_array($op)) {
1570
            // 同一字段多条件查询
1571
            array_unshift($param, $field);
1572
            $where = $param;
1573
        } elseif ($field && is_null($condition)) {
1574
            if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
1575
                // null查询
1576
                $where = [$field, $op, ''];
1577
            } elseif (in_array($op, ['=', 'eq', 'EQ', null], true)) {
1578
                $where = [$field, 'NULL', ''];
1579
            } elseif (in_array($op, ['<>', 'neq', 'NEQ'], true)) {
1580
                $where = [$field, 'NOTNULL', ''];
1581
            } else {
1582
                // 字段相等查询
1583
                $where = [$field, '=', $op];
1584
            }
1585
        } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
1586
            $where = [$field, $op, is_string($condition) ? $this->raw($condition) : $condition];
1587
        } else {
1588
            $where = $field ? [$field, $op, $condition, isset($param[2]) ? $param[2] : null] : null;
1589
        }
1590
1591
        return $where;
1592
    }
1593
1594
    /**
1595
     * 数组批量查询
1596
     * @access protected
1597
     * @param  array    $field     批量查询
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
1598
     * @param  string   $logic     查询逻辑 and or xor
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
1599
     * @return $this
1600
     */
1601
    protected function parseArrayWhereItems($field, $logic)
1602
    {
1603
        if (key($field) !== 0) {
1604
            $where = [];
1605
            foreach ($field as $key => $val) {
1606
                if ($val instanceof Expression) {
1607
                    $where[] = [$key, 'exp', $val];
1608
                } elseif (is_null($val)) {
1609
                    $where[] = [$key, 'NULL', ''];
1610
                } else {
1611
                    $where[] = [$key, is_array($val) ? 'IN' : '=', $val];
1612
                }
1613
            }
1614
        } else {
1615
            // 数组批量查询
1616
            $where = $field;
1617
        }
1618
1619
        if (!empty($where)) {
1620
            $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
1621
        }
1622
1623
        return $this;
1624
    }
1625
1626
    /**
1627
     * 去除某个查询条件
1628
     * @access public
1629
     * @param  string $field 查询字段
1630
     * @param  string $logic 查询逻辑 and or xor
1631
     * @return $this
1632
     */
1633
    public function removeWhereField($field, $logic = 'AND')
1634
    {
1635
        $logic = strtoupper($logic);
1636
1637
        if (isset($this->options['where'][$logic])) {
1638
            foreach ($this->options['where'][$logic] as $key => $val) {
1639
                if (is_array($val) && $val[0] == $field) {
1640
                    unset($this->options['where'][$logic][$key]);
1641
                }
1642
            }
1643
        }
1644
1645
        return $this;
1646
    }
1647
1648
    /**
1649
     * 去除查询参数
1650
     * @access public
1651
     * @param  string|bool $option 参数名 true 表示去除所有参数
1652
     * @return $this
1653
     */
1654
    public function removeOption($option = true)
1655
    {
1656
        if (true === $option) {
1657
            $this->options = [];
1658
            $this->bind    = [];
1659
        } elseif (is_string($option) && isset($this->options[$option])) {
1660
            unset($this->options[$option]);
1661
        }
1662
1663
        return $this;
1664
    }
1665
1666
    /**
1667
     * 条件查询
1668
     * @access public
1669
     * @param  mixed             $condition  满足条件(支持闭包)
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1670
     * @param  \Closure|array    $query      满足条件后执行的查询表达式(闭包或数组)
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 6 found
Loading history...
1671
     * @param  \Closure|array    $otherwise  不满足条件后执行
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
1672
     * @return $this
1673
     */
1674
    public function when($condition, $query, $otherwise = null)
1675
    {
1676
        if ($condition instanceof \Closure) {
1677
            $condition = $condition($this);
1678
        }
1679
1680
        if ($condition) {
1681
            if ($query instanceof \Closure) {
1682
                $query($this, $condition);
1683
            } elseif (is_array($query)) {
0 ignored issues
show
introduced by
The condition is_array($query) is always true.
Loading history...
1684
                $this->where($query);
1685
            }
1686
        } elseif ($otherwise) {
1687
            if ($otherwise instanceof \Closure) {
1688
                $otherwise($this, $condition);
1689
            } elseif (is_array($otherwise)) {
0 ignored issues
show
introduced by
The condition is_array($otherwise) is always true.
Loading history...
1690
                $this->where($otherwise);
1691
            }
1692
        }
1693
1694
        return $this;
1695
    }
1696
1697
    /**
1698
     * 指定查询数量
1699
     * @access public
1700
     * @param  mixed $offset 起始位置
1701
     * @param  mixed $length 查询数量
1702
     * @return $this
1703
     */
1704
    public function limit($offset, $length = null)
1705
    {
1706
        if (is_null($length) && strpos($offset, ',')) {
1707
            list($offset, $length) = explode(',', $offset);
1708
        }
1709
1710
        $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : '');
1711
1712
        return $this;
1713
    }
1714
1715
    /**
1716
     * 指定分页
1717
     * @access public
1718
     * @param  mixed $page     页数
1719
     * @param  mixed $listRows 每页数量
1720
     * @return $this
1721
     */
1722
    public function page($page, $listRows = null)
1723
    {
1724
        if (is_null($listRows) && strpos($page, ',')) {
1725
            list($page, $listRows) = explode(',', $page);
1726
        }
1727
1728
        $this->options['page'] = [intval($page), intval($listRows)];
1729
1730
        return $this;
1731
    }
1732
1733
    /**
1734
     * 分页查询
1735
     * @access public
1736
     * @param  int|array $listRows 每页数量 数组表示配置参数
1737
     * @param  int|bool  $simple   是否简洁模式或者总记录数
1738
     * @param  array     $config   配置参数
1739
     *                            page:当前页,
1740
     *                            path:url路径,
1741
     *                            query:url额外参数,
1742
     *                            fragment:url锚点,
1743
     *                            var_page:分页变量,
1744
     *                            list_rows:每页数量
1745
     *                            type:分页类名
1746
     * @return \think\Paginator
1747
     * @throws DbException
1748
     */
1749
    public function paginate($listRows = null, $simple = false, $config = [])
1750
    {
1751
        if (is_int($simple)) {
1752
            $total  = $simple;
1753
            $simple = false;
1754
        }
1755
1756
        $paginate = Container::get('config')->pull('paginate');
1757
1758
        if (is_array($listRows)) {
1759
            $config   = array_merge($paginate, $listRows);
1760
            $listRows = $config['list_rows'];
1761
        } else {
1762
            $config   = array_merge($paginate, $config);
1763
            $listRows = $listRows ?: $config['list_rows'];
1764
        }
1765
1766
        /** @var Paginator $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...
1767
        $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);
1768
        $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...
1769
            $class,
1770
            'getCurrentPage',
1771
        ], $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...
1772
1773
        $page = $page < 1 ? 1 : $page;
1774
1775
        $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']);
1776
1777
        if (!isset($total) && !$simple) {
1778
            $options = $this->getOptions();
1779
1780
            unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
1781
1782
            $bind    = $this->bind;
1783
            $total   = $this->count();
1784
            $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
1785
        } elseif ($simple) {
1786
            $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
1787
            $total   = null;
1788
        } else {
1789
            $results = $this->page($page, $listRows)->select();
1790
        }
1791
1792
        $this->removeOption('limit');
1793
        $this->removeOption('page');
1794
1795
        return $class::make($results, $listRows, $page, $total, $simple, $config);
0 ignored issues
show
Bug introduced by
It seems like $page can also be of type integer; however, parameter $currentPage of think\Paginator::make() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

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

1795
        return $class::make($results, $listRows, /** @scrutinizer ignore-type */ $page, $total, $simple, $config);
Loading history...
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...
1796
    }
1797
1798
    /**
1799
     * 指定当前操作的数据表
1800
     * @access public
1801
     * @param  mixed $table 表名
1802
     * @return $this
1803
     */
1804
    public function table($table)
1805
    {
1806
        if (is_string($table)) {
1807
            if (strpos($table, ')')) {
1808
                // 子查询
1809
            } elseif (strpos($table, ',')) {
1810
                $tables = explode(',', $table);
1811
                $table  = [];
1812
1813
                foreach ($tables as $item) {
1814
                    list($item, $alias) = explode(' ', trim($item));
1815
                    if ($alias) {
1816
                        $this->alias([$item => $alias]);
1817
                        $table[$item] = $alias;
1818
                    } else {
1819
                        $table[] = $item;
1820
                    }
1821
                }
1822
            } elseif (strpos($table, ' ')) {
1823
                list($table, $alias) = explode(' ', $table);
1824
1825
                $table = [$table => $alias];
1826
                $this->alias($table);
1827
            }
1828
        } else {
1829
            $tables = $table;
1830
            $table  = [];
1831
1832
            foreach ($tables as $key => $val) {
1833
                if (is_numeric($key)) {
1834
                    $table[] = $val;
1835
                } else {
1836
                    $this->alias([$key => $val]);
1837
                    $table[$key] = $val;
1838
                }
1839
            }
1840
        }
1841
1842
        $this->options['table'] = $table;
1843
1844
        return $this;
1845
    }
1846
1847
    /**
1848
     * USING支持 用于多表删除
1849
     * @access public
1850
     * @param  mixed $using
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1851
     * @return $this
1852
     */
1853
    public function using($using)
1854
    {
1855
        $this->options['using'] = $using;
1856
        return $this;
1857
    }
1858
1859
    /**
1860
     * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
1861
     * @access public
1862
     * @param  string|array $field 排序字段
1863
     * @param  string       $order 排序
1864
     * @return $this
1865
     */
1866
    public function order($field, $order = null)
1867
    {
1868
        if (empty($field)) {
1869
            return $this;
1870
        } elseif ($field instanceof Expression) {
0 ignored issues
show
introduced by
$field is never a sub-type of think\db\Expression.
Loading history...
1871
            $this->options['order'][] = $field;
1872
            return $this;
1873
        }
1874
1875
        if (is_string($field)) {
1876
            if (!empty($this->options['via'])) {
1877
                $field = $this->options['via'] . '.' . $field;
1878
            }
1879
1880
            if (strpos($field, ',')) {
1881
                $field = array_map('trim', explode(',', $field));
1882
            } else {
1883
                $field = empty($order) ? $field : [$field => $order];
1884
            }
1885
        } elseif (!empty($this->options['via'])) {
1886
            foreach ($field as $key => $val) {
1887
                if (is_numeric($key)) {
1888
                    $field[$key] = $this->options['via'] . '.' . $val;
1889
                } else {
1890
                    $field[$this->options['via'] . '.' . $key] = $val;
1891
                    unset($field[$key]);
1892
                }
1893
            }
1894
        }
1895
1896
        if (!isset($this->options['order'])) {
1897
            $this->options['order'] = [];
1898
        }
1899
1900
        if (is_array($field)) {
1901
            $this->options['order'] = array_merge($this->options['order'], $field);
1902
        } else {
1903
            $this->options['order'][] = $field;
1904
        }
1905
1906
        return $this;
1907
    }
1908
1909
    /**
1910
     * 表达式方式指定Field排序
1911
     * @access public
1912
     * @param  string $field 排序字段
1913
     * @param  array  $bind  参数绑定
1914
     * @return $this
1915
     */
1916
    public function orderRaw($field, $bind = [])
1917
    {
1918
        if ($bind) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bind of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1919
            $this->bindParams($field, $bind);
1920
        }
1921
1922
        $this->options['order'][] = $this->raw($field);
1923
1924
        return $this;
1925
    }
1926
1927
    /**
1928
     * 指定Field排序 order('id',[1,2,3],'desc')
1929
     * @access public
1930
     * @param  string|array $field 排序字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
1931
     * @param  array        $values 排序值
1932
     * @param  string       $order
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
1933
     * @return $this
1934
     */
1935
    public function orderField($field, array $values, $order = '')
1936
    {
1937
        if (!empty($values)) {
1938
            $values['sort'] = $order;
1939
1940
            $this->options['order'][$field] = $values;
1941
        }
1942
1943
        return $this;
1944
    }
1945
1946
    /**
1947
     * 随机排序
1948
     * @access public
1949
     * @return $this
1950
     */
1951
    public function orderRand()
1952
    {
1953
        $this->options['order'][] = '[rand]';
1954
        return $this;
1955
    }
1956
1957
    /**
1958
     * 查询缓存
1959
     * @access public
1960
     * @param  mixed             $key    缓存key
1961
     * @param  integer|\DateTime $expire 缓存有效期
1962
     * @param  string            $tag    缓存标签
1963
     * @return $this
1964
     */
1965
    public function cache($key = true, $expire = null, $tag = null)
1966
    {
1967
        // 增加快捷调用方式 cache(10) 等同于 cache(true, 10)
1968
        if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) {
1969
            $expire = $key;
1970
            $key    = true;
1971
        }
1972
1973
        if (false !== $key) {
1974
            $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag];
1975
        }
1976
1977
        return $this;
1978
    }
1979
1980
    /**
1981
     * 指定group查询
1982
     * @access public
1983
     * @param  string|array $group GROUP
1984
     * @return $this
1985
     */
1986
    public function group($group)
1987
    {
1988
        $this->options['group'] = $group;
1989
        return $this;
1990
    }
1991
1992
    /**
1993
     * 指定having查询
1994
     * @access public
1995
     * @param  string $having having
1996
     * @return $this
1997
     */
1998
    public function having($having)
1999
    {
2000
        $this->options['having'] = $having;
2001
        return $this;
2002
    }
2003
2004
    /**
2005
     * 指定查询lock
2006
     * @access public
2007
     * @param  bool|string $lock 是否lock
2008
     * @return $this
2009
     */
2010
    public function lock($lock = false)
2011
    {
2012
        $this->options['lock']   = $lock;
2013
        $this->options['master'] = true;
2014
2015
        return $this;
2016
    }
2017
2018
    /**
2019
     * 指定distinct查询
2020
     * @access public
2021
     * @param  string $distinct 是否唯一
2022
     * @return $this
2023
     */
2024
    public function distinct($distinct)
2025
    {
2026
        $this->options['distinct'] = $distinct;
2027
        return $this;
2028
    }
2029
2030
    /**
2031
     * 指定数据表别名
2032
     * @access public
2033
     * @param  array|string $alias 数据表别名
2034
     * @return $this
2035
     */
2036
    public function alias($alias)
2037
    {
2038
        if (is_array($alias)) {
2039
            foreach ($alias as $key => $val) {
2040
                if (false !== strpos($key, '__')) {
2041
                    $table = $this->connection->parseSqlTable($key);
2042
                } else {
2043
                    $table = $key;
2044
                }
2045
                $this->options['alias'][$table] = $val;
2046
            }
2047
        } else {
2048
            if (isset($this->options['table'])) {
2049
                $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
2050
                if (false !== strpos($table, '__')) {
2051
                    $table = $this->connection->parseSqlTable($table);
2052
                }
2053
            } else {
2054
                $table = $this->getTable();
2055
            }
2056
2057
            $this->options['alias'][$table] = $alias;
2058
        }
2059
2060
        return $this;
2061
    }
2062
2063
    /**
2064
     * 指定强制索引
2065
     * @access public
2066
     * @param  string $force 索引名称
2067
     * @return $this
2068
     */
2069
    public function force($force)
2070
    {
2071
        $this->options['force'] = $force;
2072
        return $this;
2073
    }
2074
2075
    /**
2076
     * 查询注释
2077
     * @access public
2078
     * @param  string $comment 注释
2079
     * @return $this
2080
     */
2081
    public function comment($comment)
2082
    {
2083
        $this->options['comment'] = $comment;
2084
        return $this;
2085
    }
2086
2087
    /**
2088
     * 获取执行的SQL语句
2089
     * @access public
2090
     * @param  boolean $fetch 是否返回sql
2091
     * @return $this
2092
     */
2093
    public function fetchSql($fetch = true)
2094
    {
2095
        $this->options['fetch_sql'] = $fetch;
2096
        return $this;
2097
    }
2098
2099
    /**
2100
     * 不主动获取数据集
2101
     * @access public
2102
     * @param  bool $pdo 是否返回 PDOStatement 对象
2103
     * @return $this
2104
     */
2105
    public function fetchPdo($pdo = true)
2106
    {
2107
        $this->options['fetch_pdo'] = $pdo;
2108
        return $this;
2109
    }
2110
2111
    /**
2112
     * 设置是否返回数据集对象(支持设置数据集对象类名)
2113
     * @access public
2114
     * @param  bool|string  $collection  是否返回数据集对象
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
2115
     * @return $this
2116
     */
2117
    public function fetchCollection($collection = true)
2118
    {
2119
        $this->options['collection'] = $collection;
2120
2121
        return $this;
2122
    }
2123
2124
    /**
2125
     * 设置从主服务器读取数据
2126
     * @access public
2127
     * @return $this
2128
     */
2129
    public function master()
2130
    {
2131
        $this->options['master'] = true;
2132
        return $this;
2133
    }
2134
2135
    /**
2136
     * 设置是否严格检查字段名
2137
     * @access public
2138
     * @param  bool $strict 是否严格检查字段
2139
     * @return $this
2140
     */
2141
    public function strict($strict = true)
2142
    {
2143
        $this->options['strict'] = $strict;
2144
        return $this;
2145
    }
2146
2147
    /**
2148
     * 设置查询数据不存在是否抛出异常
2149
     * @access public
2150
     * @param  bool $fail 数据不存在是否抛出异常
2151
     * @return $this
2152
     */
2153
    public function failException($fail = true)
2154
    {
2155
        $this->options['fail'] = $fail;
2156
        return $this;
2157
    }
2158
2159
    /**
2160
     * 设置自增序列名
2161
     * @access public
2162
     * @param  string $sequence 自增序列名
2163
     * @return $this
2164
     */
2165
    public function sequence($sequence = null)
2166
    {
2167
        $this->options['sequence'] = $sequence;
2168
        return $this;
2169
    }
2170
2171
    /**
2172
     * 设置需要隐藏的输出属性
2173
     * @access public
2174
     * @param  mixed $hidden 需要隐藏的字段名
2175
     * @return $this
2176
     */
2177
    public function hidden($hidden)
2178
    {
2179
        if ($this->model) {
2180
            $this->options['hidden'] = $hidden;
2181
            return $this;
2182
        }
2183
2184
        return $this->field($hidden, true);
2185
    }
2186
2187
    /**
2188
     * 设置需要输出的属性
2189
     * @access public
2190
     * @param  array $visible 需要输出的属性
2191
     * @return $this
2192
     */
2193
    public function visible(array $visible)
2194
    {
2195
        $this->options['visible'] = $visible;
2196
        return $this;
2197
    }
2198
2199
    /**
2200
     * 设置需要追加输出的属性
2201
     * @access public
2202
     * @param  array $append 需要追加的属性
2203
     * @return $this
2204
     */
2205
    public function append(array $append)
2206
    {
2207
        $this->options['append'] = $append;
2208
        return $this;
2209
    }
2210
2211
    /**
2212
     * 设置数据字段获取器
2213
     * @access public
2214
     * @param  string|array $name       字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 7 found
Loading history...
2215
     * @param  callable     $callback   闭包获取器
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
2216
     * @return $this
2217
     */
2218
    public function withAttr($name, $callback = null)
2219
    {
2220
        if (is_array($name)) {
2221
            $this->options['with_attr'] = $name;
2222
        } else {
2223
            $this->options['with_attr'][$name] = $callback;
2224
        }
2225
2226
        return $this;
2227
    }
2228
2229
    /**
2230
     * 设置JSON字段信息
2231
     * @access public
2232
     * @param  array $json JSON字段
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
2233
     * @param  bool  $assoc 是否取出数组
2234
     * @return $this
2235
     */
2236
    public function json(array $json = [], $assoc = false)
2237
    {
2238
        $this->options['json']       = $json;
2239
        $this->options['json_assoc'] = $assoc;
2240
        return $this;
2241
    }
2242
2243
    /**
2244
     * 设置字段类型信息
2245
     * @access public
2246
     * @param  array $type 字段类型信息
2247
     * @return $this
2248
     */
2249
    public function setJsonFieldType(array $type)
2250
    {
2251
        $this->options['field_type'] = $type;
2252
        return $this;
2253
    }
2254
2255
    /**
2256
     * 获取字段类型信息
2257
     * @access public
2258
     * @param  string $field 字段名
2259
     * @return string|null
2260
     */
2261
    public function getJsonFieldType($field)
2262
    {
2263
        return isset($this->options['field_type'][$field]) ? $this->options['field_type'][$field] : null;
2264
    }
2265
2266
    /**
2267
     * 是否允许返回空数据(或空模型)
2268
     * @access public
2269
     * @param  bool $allowEmpty 是否允许为空
2270
     * @return $this
2271
     */
2272
    public function allowEmpty($allowEmpty = true)
2273
    {
2274
        $this->options['allow_empty'] = $allowEmpty;
2275
        return $this;
2276
    }
2277
2278
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
2279
     * 添加查询范围
2280
     * @access public
2281
     * @param  array|string|\Closure   $scope 查询范围定义
2282
     * @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...
2283
     * @return $this
2284
     */
2285
    public function scope($scope, ...$args)
2286
    {
2287
        // 查询范围的第一个参数始终是当前查询对象
2288
        array_unshift($args, $this);
2289
2290
        if ($scope instanceof \Closure) {
2291
            call_user_func_array($scope, $args);
2292
            return $this;
2293
        }
2294
2295
        if (is_string($scope)) {
2296
            $scope = explode(',', $scope);
2297
        }
2298
2299
        if ($this->model) {
2300
            // 检查模型类的查询范围方法
2301
            foreach ($scope as $name) {
2302
                $method = 'scope' . trim($name);
2303
2304
                if (method_exists($this->model, $method)) {
2305
                    call_user_func_array([$this->model, $method], $args);
2306
                }
2307
            }
2308
        }
2309
2310
        return $this;
2311
    }
2312
2313
    /**
2314
     * 使用搜索器条件搜索字段
2315
     * @access public
2316
     * @param  array    $fields     搜索字段
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
2317
     * @param  array    $data       搜索数据
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 7 found
Loading history...
2318
     * @param  string   $prefix     字段前缀标识
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
2319
     * @return $this
2320
     */
2321
    public function withSearch(array $fields, array $data = [], $prefix = '')
2322
    {
2323
        foreach ($fields as $key => $field) {
2324
            if ($field instanceof \Closure) {
2325
                $field($this, isset($data[$key]) ? $data[$key] : null, $data, $prefix);
2326
            } elseif ($this->model) {
2327
                // 检测搜索器
2328
                $fieldName = is_numeric($key) ? $field : $key;
2329
                $method    = 'search' . Loader::parseName($fieldName, 1) . 'Attr';
2330
2331
                if (method_exists($this->model, $method)) {
2332
                    $this->model->$method($this, isset($data[$field]) ? $data[$field] : null, $data, $prefix);
2333
                }
2334
            }
2335
        }
2336
2337
        return $this;
2338
    }
2339
2340
    /**
2341
     * 指定数据表主键
2342
     * @access public
2343
     * @param  string $pk 主键
2344
     * @return $this
2345
     */
2346
    public function pk($pk)
2347
    {
2348
        $this->pk = $pk;
2349
        return $this;
2350
    }
2351
2352
    /**
2353
     * 查询日期或者时间
2354
     * @access public
2355
     * @param  string       $name  时间表达式
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
2356
     * @param  string|array $rule  时间范围
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
2357
     * @return $this
2358
     */
2359
    public function timeRule($name, $rule)
2360
    {
2361
        $this->timeRule[$name] = $rule;
2362
        return $this;
2363
    }
2364
2365
    /**
2366
     * 查询日期或者时间
2367
     * @access public
2368
     * @param  string       $field 日期字段名
2369
     * @param  string|array $op    比较运算符或者表达式
2370
     * @param  string|array $range 比较范围
2371
     * @param  string       $logic AND OR
2372
     * @return $this
2373
     */
2374
    public function whereTime($field, $op, $range = null, $logic = 'AND')
2375
    {
2376
        if (is_null($range)) {
2377
            if (is_array($op)) {
2378
                $range = $op;
2379
            } else {
2380
                if (isset($this->timeExp[strtolower($op)])) {
2381
                    $op = $this->timeExp[strtolower($op)];
2382
                }
2383
2384
                if (isset($this->timeRule[strtolower($op)])) {
2385
                    $range = $this->timeRule[strtolower($op)];
2386
                } else {
2387
                    $range = $op;
2388
                }
2389
            }
2390
2391
            $op = is_array($range) ? 'between' : '>=';
2392
        }
2393
2394
        return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
0 ignored issues
show
Bug introduced by
It seems like $op can also be of type array; however, parameter $str of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

2394
        return $this->parseWhereExp($logic, $field, strtolower(/** @scrutinizer ignore-type */ $op) . ' time', $range, [], true);
Loading history...
2395
    }
2396
2397
    /**
2398
     * 查询当前时间在两个时间字段范围
2399
     * @access public
2400
     * @param  string    $startField    开始时间字段
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2401
     * @param  string    $endField 结束时间字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2402
     * @return $this
2403
     */
2404
    public function whereBetweenTimeField($startField, $endField)
2405
    {
2406
        return $this->whereTime($startField, '<=', time())
2407
            ->whereTime($endField, '>=', time());
2408
    }
2409
2410
    /**
2411
     * 查询当前时间不在两个时间字段范围
2412
     * @access public
2413
     * @param  string    $startField    开始时间字段
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2414
     * @param  string    $endField 结束时间字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2415
     * @return $this
2416
     */
2417
    public function whereNotBetweenTimeField($startField, $endField)
2418
    {
2419
        return $this->whereTime($startField, '>', time())
2420
            ->whereTime($endField, '<', time(), 'OR');
2421
    }
2422
2423
    /**
2424
     * 查询日期或者时间范围
2425
     * @access public
2426
     * @param  string    $field 日期字段名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2427
     * @param  string    $startTime    开始时间
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2428
     * @param  string    $endTime 结束时间
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
2429
     * @param  string    $logic AND OR
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2430
     * @return $this
2431
     */
2432
    public function whereBetweenTime($field, $startTime, $endTime = null, $logic = 'AND')
2433
    {
2434
        if (is_null($endTime)) {
2435
            $time    = is_string($startTime) ? strtotime($startTime) : $startTime;
0 ignored issues
show
introduced by
The condition is_string($startTime) is always true.
Loading history...
2436
            $endTime = strtotime('+1 day', $time);
2437
        }
2438
2439
        return $this->parseWhereExp($logic, $field, 'between time', [$startTime, $endTime], [], true);
2440
    }
2441
2442
    /**
2443
     * 获取当前数据表的主键
2444
     * @access public
2445
     * @param  string|array $options 数据表名或者查询参数
2446
     * @return string|array
2447
     */
2448
    public function getPk($options = '')
2449
    {
2450
        if (!empty($this->pk)) {
2451
            $pk = $this->pk;
2452
        } else {
2453
            $pk = $this->connection->getPk(is_array($options) && isset($options['table']) ? $options['table'] : $this->getTable());
2454
        }
2455
2456
        return $pk;
2457
    }
2458
2459
    /**
2460
     * 参数绑定
2461
     * @access public
2462
     * @param  mixed   $value 绑定变量值
2463
     * @param  integer $type  绑定类型
2464
     * @param  string  $name  绑定名称
2465
     * @return $this|string
2466
     */
2467
    public function bind($value, $type = PDO::PARAM_STR, $name = null)
2468
    {
2469
        if (is_array($value)) {
2470
            $this->bind = array_merge($this->bind, $value);
2471
        } else {
2472
            $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_';
2473
2474
            $this->bind[$name] = [$value, $type];
2475
            return $name;
2476
        }
2477
2478
        return $this;
2479
    }
2480
2481
    /**
2482
     * 检测参数是否已经绑定
2483
     * @access public
2484
     * @param  string $key 参数名
2485
     * @return bool
2486
     */
2487
    public function isBind($key)
2488
    {
2489
        return isset($this->bind[$key]);
2490
    }
2491
2492
    /**
2493
     * 查询参数赋值
2494
     * @access public
2495
     * @param  string $name     参数名
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 5 found
Loading history...
2496
     * @param  mixed  $value    值
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
2497
     * @return $this
2498
     */
2499
    public function option($name, $value)
2500
    {
2501
        $this->options[$name] = $value;
2502
        return $this;
2503
    }
2504
2505
    /**
2506
     * 查询参数赋值
2507
     * @access protected
2508
     * @param  array $options 表达式参数
2509
     * @return $this
2510
     */
2511
    protected function options(array $options)
2512
    {
2513
        $this->options = $options;
2514
        return $this;
2515
    }
2516
2517
    /**
2518
     * 获取当前的查询参数
2519
     * @access public
2520
     * @param  string $name 参数名
2521
     * @return mixed
2522
     */
2523
    public function getOptions($name = '')
2524
    {
2525
        if ('' === $name) {
2526
            return $this->options;
2527
        }
2528
        return isset($this->options[$name]) ? $this->options[$name] : null;
2529
    }
2530
2531
    /**
2532
     * 设置当前的查询参数
2533
     * @access public
2534
     * @param  string $option 参数名
2535
     * @param  mixed  $value  参数值
2536
     * @return $this
2537
     */
2538
    public function setOption($option, $value)
2539
    {
2540
        $this->options[$option] = $value;
2541
        return $this;
2542
    }
2543
2544
    /**
2545
     * 设置关联查询JOIN预查询
2546
     * @access public
2547
     * @param  string|array $with 关联方法名称
2548
     * @return $this
2549
     */
2550
    public function with($with)
2551
    {
2552
        if (empty($with)) {
2553
            return $this;
2554
        }
2555
2556
        if (is_string($with)) {
2557
            $with = explode(',', $with);
2558
        }
2559
2560
        $first = true;
2561
2562
        /** @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...
2563
        $class = $this->model;
2564
        foreach ($with as $key => $relation) {
2565
            $closure = null;
2566
2567
            if ($relation instanceof \Closure) {
2568
                // 支持闭包查询过滤关联条件
2569
                $closure  = $relation;
2570
                $relation = $key;
2571
            } elseif (is_array($relation)) {
2572
                $relation = $key;
2573
            } elseif (is_string($relation) && strpos($relation, '.')) {
2574
                list($relation, $subRelation) = explode('.', $relation, 2);
2575
            }
2576
2577
            /** @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...
2578
            $relation = Loader::parseName($relation, 1, false);
2579
            $model    = $class->$relation();
2580
2581
            if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) {
2582
                $table = $model->getTable();
0 ignored issues
show
Bug introduced by
The method getTable() does not exist on think\model\relation\OneToOne. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

2582
                /** @scrutinizer ignore-call */ 
2583
                $table = $model->getTable();
Loading history...
2583
                $model->removeOption()
0 ignored issues
show
Bug introduced by
The method removeOption() does not exist on think\model\relation\OneToOne. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

2583
                $model->/** @scrutinizer ignore-call */ 
2584
                        removeOption()
Loading history...
2584
                    ->table($table)
0 ignored issues
show
Bug introduced by
The method table() does not exist on think\model\relation\OneToOne. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

2584
                    ->/** @scrutinizer ignore-call */ table($table)
Loading history...
2585
                    ->eagerly($this, $relation, true, '', $closure, $first);
2586
                $first = false;
2587
            }
2588
        }
2589
2590
        $this->via();
2591
2592
        $this->options['with'] = $with;
2593
2594
        return $this;
2595
    }
2596
2597
    /**
2598
     * 关联预载入 JOIN方式(不支持嵌套)
2599
     * @access protected
2600
     * @param  string|array $with 关联方法名
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2601
     * @param  string       $joinType JOIN方式
2602
     * @return $this
2603
     */
2604
    public function withJoin($with, $joinType = '')
2605
    {
2606
        if (empty($with)) {
2607
            return $this;
2608
        }
2609
2610
        if (is_string($with)) {
2611
            $with = explode(',', $with);
2612
        }
2613
2614
        $first = true;
2615
2616
        /** @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...
2617
        $class = $this->model;
2618
        foreach ($with as $key => $relation) {
2619
            $closure = null;
2620
            $field   = true;
2621
2622
            if ($relation instanceof \Closure) {
2623
                // 支持闭包查询过滤关联条件
2624
                $closure  = $relation;
2625
                $relation = $key;
2626
            } elseif (is_array($relation)) {
2627
                $field    = $relation;
2628
                $relation = $key;
2629
            } elseif (is_string($relation) && strpos($relation, '.')) {
2630
                list($relation, $subRelation) = explode('.', $relation, 2);
2631
            }
2632
2633
            /** @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...
2634
            $relation = Loader::parseName($relation, 1, false);
2635
            $model    = $class->$relation();
2636
2637
            if ($model instanceof OneToOne) {
2638
                $model->eagerly($this, $relation, $field, $joinType, $closure, $first);
2639
                $first = false;
2640
            } else {
2641
                // 不支持其它关联
2642
                unset($with[$key]);
2643
            }
2644
        }
2645
2646
        $this->via();
2647
2648
        $this->options['with_join'] = $with;
2649
2650
        return $this;
2651
    }
2652
2653
    /**
2654
     * 关联统计
2655
     * @access protected
2656
     * @param  string|array $relation 关联方法名
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
2657
     * @param  string       $aggregate 聚合查询方法
2658
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
2659
     * @param  bool         $subQuery 是否使用子查询
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
2660
     * @return $this
2661
     */
2662
    protected function withAggregate($relation, $aggregate = 'count', $field = '*', $subQuery = true)
2663
    {
2664
        $relations = is_string($relation) ? explode(',', $relation) : $relation;
2665
2666
        if (!$subQuery) {
2667
            $this->options['with_count'][] = [$relations, $aggregate, $field];
2668
        } else {
2669
            if (!isset($this->options['field'])) {
2670
                $this->field('*');
2671
            }
2672
2673
            foreach ($relations as $key => $relation) {
0 ignored issues
show
introduced by
$relation is overwriting one of the parameters of this function.
Loading history...
2674
                $closure = $aggregateField = null;
2675
2676
                if ($relation instanceof \Closure) {
2677
                    $closure  = $relation;
2678
                    $relation = $key;
2679
                } elseif (!is_int($key)) {
2680
                    $aggregateField = $relation;
2681
                    $relation       = $key;
2682
                }
2683
2684
                $relation = Loader::parseName($relation, 1, false);
2685
2686
                $count = $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField);
2687
2688
                if (empty($aggregateField)) {
2689
                    $aggregateField = Loader::parseName($relation) . '_' . $aggregate;
2690
                }
2691
2692
                $this->field(['(' . $count . ')' => $aggregateField]);
2693
            }
2694
        }
2695
2696
        return $this;
2697
    }
2698
2699
    /**
2700
     * 关联统计
2701
     * @access public
2702
     * @param  string|array $relation 关联方法名
2703
     * @param  bool         $subQuery 是否使用子查询
2704
     * @return $this
2705
     */
2706
    public function withCount($relation, $subQuery = true)
2707
    {
2708
        return $this->withAggregate($relation, 'count', '*', $subQuery);
2709
    }
2710
2711
    /**
2712
     * 关联统计Sum
2713
     * @access public
2714
     * @param  string|array $relation 关联方法名
2715
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2716
     * @param  bool         $subQuery 是否使用子查询
2717
     * @return $this
2718
     */
2719
    public function withSum($relation, $field, $subQuery = true)
2720
    {
2721
        return $this->withAggregate($relation, 'sum', $field, $subQuery);
2722
    }
2723
2724
    /**
2725
     * 关联统计Max
2726
     * @access public
2727
     * @param  string|array $relation 关联方法名
2728
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2729
     * @param  bool         $subQuery 是否使用子查询
2730
     * @return $this
2731
     */
2732
    public function withMax($relation, $field, $subQuery = true)
2733
    {
2734
        return $this->withAggregate($relation, 'max', $field, $subQuery);
2735
    }
2736
2737
    /**
2738
     * 关联统计Min
2739
     * @access public
2740
     * @param  string|array $relation 关联方法名
2741
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2742
     * @param  bool         $subQuery 是否使用子查询
2743
     * @return $this
2744
     */
2745
    public function withMin($relation, $field, $subQuery = true)
2746
    {
2747
        return $this->withAggregate($relation, 'min', $field, $subQuery);
2748
    }
2749
2750
    /**
2751
     * 关联统计Avg
2752
     * @access public
2753
     * @param  string|array $relation 关联方法名
2754
     * @param  string       $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
2755
     * @param  bool         $subQuery 是否使用子查询
2756
     * @return $this
2757
     */
2758
    public function withAvg($relation, $field, $subQuery = true)
2759
    {
2760
        return $this->withAggregate($relation, 'avg', $field, $subQuery);
2761
    }
2762
2763
    /**
2764
     * 关联预加载中 获取关联指定字段值
2765
     * example:
2766
     * Model::with(['relation' => function($query){
2767
     *     $query->withField("id,name");
2768
     * }])
2769
     *
2770
     * @access public
2771
     * @param  string | array $field 指定获取的字段
2772
     * @return $this
2773
     */
2774
    public function withField($field)
2775
    {
2776
        $this->options['with_field'] = $field;
2777
2778
        return $this;
2779
    }
2780
2781
    /**
2782
     * 设置当前字段添加的表别名
2783
     * @access public
2784
     * @param  string $via
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
2785
     * @return $this
2786
     */
2787
    public function via($via = '')
2788
    {
2789
        $this->options['via'] = $via;
2790
2791
        return $this;
2792
    }
2793
2794
    /**
2795
     * 设置关联查询
2796
     * @access public
2797
     * @param  string|array $relation 关联名称
2798
     * @return $this
2799
     */
2800
    public function relation($relation)
2801
    {
2802
        if (empty($relation)) {
2803
            return $this;
2804
        }
2805
2806
        if (is_string($relation)) {
2807
            $relation = explode(',', $relation);
2808
        }
2809
2810
        if (isset($this->options['relation'])) {
2811
            $this->options['relation'] = array_merge($this->options['relation'], $relation);
2812
        } else {
2813
            $this->options['relation'] = $relation;
2814
        }
2815
2816
        return $this;
2817
    }
2818
2819
    /**
2820
     * 插入记录
2821
     * @access public
2822
     * @param  array   $data         数据
2823
     * @param  boolean $replace      是否replace
2824
     * @param  boolean $getLastInsID 返回自增主键
2825
     * @param  string  $sequence     自增序列名
2826
     * @return integer|string
2827
     */
2828
    public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null)
2829
    {
2830
        $this->parseOptions();
2831
2832
        $this->options['data'] = array_merge($this->options['data'], $data);
2833
2834
        return $this->connection->insert($this, $replace, $getLastInsID, $sequence);
2835
    }
2836
2837
    /**
2838
     * 插入记录并获取自增ID
2839
     * @access public
2840
     * @param  array   $data     数据
2841
     * @param  boolean $replace  是否replace
2842
     * @param  string  $sequence 自增序列名
2843
     * @return integer|string
2844
     */
2845
    public function insertGetId(array $data, $replace = false, $sequence = null)
2846
    {
2847
        return $this->insert($data, $replace, true, $sequence);
2848
    }
2849
2850
    /**
2851
     * 批量插入记录
2852
     * @access public
2853
     * @param  array     $dataSet 数据集
2854
     * @param  boolean   $replace 是否replace
2855
     * @param  integer   $limit   每次写入数据限制
2856
     * @return integer|string
2857
     */
2858
    public function insertAll(array $dataSet = [], $replace = false, $limit = null)
2859
    {
2860
        $this->parseOptions();
2861
2862
        if (empty($dataSet)) {
2863
            $dataSet = $this->options['data'];
2864
        }
2865
2866
        if (empty($limit) && !empty($this->options['limit'])) {
2867
            $limit = $this->options['limit'];
2868
        }
2869
2870
        return $this->connection->insertAll($this, $dataSet, $replace, $limit);
2871
    }
2872
2873
    /**
2874
     * 通过Select方式插入记录
2875
     * @access public
2876
     * @param  string $fields 要插入的数据表字段名
2877
     * @param  string $table  要插入的数据表名
2878
     * @return integer|string
2879
     * @throws PDOException
2880
     */
2881
    public function selectInsert($fields, $table)
2882
    {
2883
        $this->parseOptions();
2884
2885
        return $this->connection->selectInsert($this, $fields, $table);
2886
    }
2887
2888
    /**
2889
     * 更新记录
2890
     * @access public
2891
     * @param  mixed $data 数据
2892
     * @return integer|string
2893
     * @throws Exception
2894
     * @throws PDOException
2895
     */
2896
    public function update(array $data = [])
2897
    {
2898
        $this->parseOptions();
2899
2900
        $this->options['data'] = array_merge($this->options['data'], $data);
2901
2902
        return $this->connection->update($this);
2903
    }
2904
2905
    /**
2906
     * 删除记录
2907
     * @access public
2908
     * @param  mixed $data 表达式 true 表示强制删除
2909
     * @return int
2910
     * @throws Exception
2911
     * @throws PDOException
2912
     */
2913
    public function delete($data = null)
2914
    {
2915
        $this->parseOptions();
2916
2917
        if (!is_null($data) && true !== $data) {
2918
            // AR模式分析主键条件
2919
            $this->parsePkWhere($data);
2920
        }
2921
2922
        if (!empty($this->options['soft_delete'])) {
2923
            // 软删除
2924
            list($field, $condition) = $this->options['soft_delete'];
2925
            if ($condition) {
2926
                unset($this->options['soft_delete']);
2927
                $this->options['data'] = [$field => $condition];
2928
2929
                return $this->connection->update($this);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection->update($this) also could return the type string which is incompatible with the documented return type integer.
Loading history...
2930
            }
2931
        }
2932
2933
        $this->options['data'] = $data;
2934
2935
        return $this->connection->delete($this);
2936
    }
2937
2938
    /**
2939
     * 执行查询但只返回PDOStatement对象
2940
     * @access public
2941
     * @return \PDOStatement|string
2942
     */
2943
    public function getPdo()
2944
    {
2945
        $this->parseOptions();
2946
2947
        return $this->connection->pdo($this);
2948
    }
2949
2950
    /**
2951
     * 使用游标查找记录
2952
     * @access public
2953
     * @param  array|string|Query|\Closure $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
2954
     * @return \Generator
2955
     */
2956
    public function cursor($data = null)
2957
    {
2958
        if ($data instanceof \Closure) {
2959
            $data($this);
2960
            $data = null;
2961
        }
2962
2963
        $this->parseOptions();
2964
2965
        if (!is_null($data)) {
2966
            // 主键条件分析
2967
            $this->parsePkWhere($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type Closure and think\db\Query; however, parameter $data of think\db\Query::parsePkWhere() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

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

2967
            $this->parsePkWhere(/** @scrutinizer ignore-type */ $data);
Loading history...
2968
        }
2969
2970
        $this->options['data'] = $data;
2971
2972
        $connection = clone $this->connection;
2973
2974
        return $connection->cursor($this);
2975
    }
2976
2977
    /**
2978
     * 查找记录
2979
     * @access public
2980
     * @param  array|string|Query|\Closure $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
2981
     * @return Collection|array|\PDOStatement|string
2982
     * @throws DbException
2983
     * @throws ModelNotFoundException
2984
     * @throws DataNotFoundException
2985
     */
2986
    public function select($data = null)
2987
    {
2988
        if ($data instanceof Query) {
2989
            return $data->select();
2990
        } elseif ($data instanceof \Closure) {
2991
            $data($this);
2992
            $data = null;
2993
        }
2994
2995
        $this->parseOptions();
2996
2997
        if (false === $data) {
0 ignored issues
show
introduced by
The condition false === $data is always false.
Loading history...
2998
            // 用于子查询 不查询只返回SQL
2999
            $this->options['fetch_sql'] = true;
3000
        } elseif (!is_null($data)) {
3001
            // 主键条件分析
3002
            $this->parsePkWhere($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type Closure and think\db\Query; however, parameter $data of think\db\Query::parsePkWhere() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

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

3002
            $this->parsePkWhere(/** @scrutinizer ignore-type */ $data);
Loading history...
3003
        }
3004
3005
        $this->options['data'] = $data;
3006
3007
        $resultSet = $this->connection->select($this);
3008
3009
        if ($this->options['fetch_sql']) {
3010
            return $resultSet;
3011
        }
3012
3013
        // 返回结果处理
3014
        if (!empty($this->options['fail']) && count($resultSet) == 0) {
3015
            $this->throwNotFound($this->options);
3016
        }
3017
3018
        // 数据列表读取后的处理
3019
        if (!empty($this->model)) {
3020
            // 生成模型对象
3021
            $resultSet = $this->resultSetToModelCollection($resultSet);
3022
        } else {
3023
            $this->resultSet($resultSet);
3024
        }
3025
3026
        return $resultSet;
3027
    }
3028
3029
    /**
3030
     * 查询数据转换为模型数据集对象
3031
     * @access protected
3032
     * @param  array  $resultSet         数据集
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 9 found
Loading history...
3033
     * @return ModelCollection
3034
     */
3035
    protected function resultSetToModelCollection(array $resultSet)
3036
    {
3037
        if (!empty($this->options['collection']) && is_string($this->options['collection'])) {
3038
            $collection = $this->options['collection'];
3039
        }
3040
3041
        if (empty($resultSet)) {
3042
            return $this->model->toCollection([], isset($collection) ? $collection : null);
3043
        }
3044
3045
        // 检查动态获取器
3046
        if (!empty($this->options['with_attr'])) {
3047
            foreach ($this->options['with_attr'] as $name => $val) {
3048
                if (strpos($name, '.')) {
3049
                    list($relation, $field) = explode('.', $name);
3050
3051
                    $withRelationAttr[$relation][$field] = $val;
3052
                    unset($this->options['with_attr'][$name]);
3053
                }
3054
            }
3055
        }
3056
3057
        $withRelationAttr = isset($withRelationAttr) ? $withRelationAttr : [];
3058
3059
        foreach ($resultSet as $key => &$result) {
3060
            // 数据转换为模型对象
3061
            $this->resultToModel($result, $this->options, true, $withRelationAttr);
3062
        }
3063
3064
        if (!empty($this->options['with'])) {
3065
            // 预载入
3066
            $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 3059. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
3067
        }
3068
3069
        if (!empty($this->options['with_join'])) {
3070
            // JOIN预载入
3071
            $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true);
3072
        }
3073
3074
        // 模型数据集转换
3075
        return $result->toCollection($resultSet, isset($collection) ? $collection : null);
3076
    }
3077
3078
    /**
3079
     * 处理数据集
3080
     * @access public
3081
     * @param  array $resultSet
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
3082
     * @return void
3083
     */
3084
    protected function resultSet(&$resultSet)
3085
    {
3086
        if (!empty($this->options['json'])) {
3087
            foreach ($resultSet as &$result) {
3088
                $this->jsonResult($result, $this->options['json'], true);
3089
            }
3090
        }
3091
3092
        if (!empty($this->options['with_attr'])) {
3093
            foreach ($resultSet as &$result) {
3094
                $this->getResultAttr($result, $this->options['with_attr']);
3095
            }
3096
        }
3097
3098
        if (!empty($this->options['collection']) || 'collection' == $this->connection->getConfig('resultset_type')) {
3099
            // 返回Collection对象
3100
            $resultSet = new Collection($resultSet);
3101
        }
3102
    }
3103
3104
    /**
3105
     * 查找单条记录
3106
     * @access public
3107
     * @param  array|string|Query|\Closure $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
3108
     * @return array|null|\PDOStatement|string|Model
3109
     * @throws DbException
3110
     * @throws ModelNotFoundException
3111
     * @throws DataNotFoundException
3112
     */
3113
    public function find($data = null)
3114
    {
3115
        if ($data instanceof Query) {
3116
            return $data->find();
3117
        } elseif ($data instanceof \Closure) {
3118
            $data($this);
3119
            $data = null;
3120
        }
3121
3122
        $this->parseOptions();
3123
3124
        if (!is_null($data)) {
3125
            // AR模式分析主键条件
3126
            $this->parsePkWhere($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type Closure and think\db\Query; however, parameter $data of think\db\Query::parsePkWhere() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

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

3126
            $this->parsePkWhere(/** @scrutinizer ignore-type */ $data);
Loading history...
3127
        }
3128
3129
        $this->options['data'] = $data;
3130
3131
        $result = $this->connection->find($this);
3132
3133
        if ($this->options['fetch_sql']) {
3134
            return $result;
3135
        }
3136
3137
        // 数据处理
3138
        if (empty($result)) {
3139
            return $this->resultToEmpty();
3140
        }
3141
3142
        if (!empty($this->model)) {
3143
            // 返回模型对象
3144
            $this->resultToModel($result, $this->options);
3145
        } else {
3146
            $this->result($result);
3147
        }
3148
3149
        return $result;
3150
    }
3151
3152
    /**
3153
     * 处理空数据
3154
     * @access protected
3155
     * @return array|Model|null
3156
     * @throws DbException
3157
     * @throws ModelNotFoundException
3158
     * @throws DataNotFoundException
3159
     */
3160
    protected function resultToEmpty()
3161
    {
3162
        if (!empty($this->options['allow_empty'])) {
3163
            return !empty($this->model) ? $this->model->newInstance([], $this->getModelUpdateCondition($this->options)) : [];
3164
        } elseif (!empty($this->options['fail'])) {
3165
            $this->throwNotFound($this->options);
3166
        }
3167
    }
3168
3169
    /**
3170
     * 查找单条记录
3171
     * @access public
3172
     * @param  mixed     $data  主键值或者查询条件(闭包)
0 ignored issues
show
Coding Style introduced by
Expected 10 spaces after parameter name; 2 found
Loading history...
3173
     * @param  mixed     $with  关联预查询
0 ignored issues
show
Coding Style introduced by
Expected 10 spaces after parameter name; 2 found
Loading history...
3174
     * @param  bool      $cache 是否缓存
0 ignored issues
show
Coding Style introduced by
Expected 9 spaces after parameter name; 1 found
Loading history...
3175
     * @param  bool      $failException 是否抛出异常
3176
     * @return static|null
3177
     * @throws exception\DbException
3178
     */
3179
    public function get($data, $with = [], $cache = false, $failException = false)
3180
    {
3181
        if (is_null($data)) {
3182
            return;
3183
        }
3184
3185
        if (true === $with || is_int($with)) {
3186
            $cache = $with;
3187
            $with  = [];
3188
        }
3189
3190
        return $this->parseQuery($data, $with, $cache)
0 ignored issues
show
Bug introduced by
It seems like $cache can also be of type array; however, parameter $cache of think\db\Query::parseQuery() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

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

3190
        return $this->parseQuery($data, $with, /** @scrutinizer ignore-type */ $cache)
Loading history...
Bug introduced by
It seems like $with can also be of type array and array; however, parameter $with of think\db\Query::parseQuery() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

3190
        return $this->parseQuery($data, /** @scrutinizer ignore-type */ $with, $cache)
Loading history...
3191
            ->failException($failException)
3192
            ->find($data);
3193
    }
3194
3195
    /**
3196
     * 查找单条记录 如果不存在直接抛出异常
3197
     * @access public
3198
     * @param  mixed     $data  主键值或者查询条件(闭包)
3199
     * @param  mixed     $with  关联预查询
3200
     * @param  bool      $cache 是否缓存
3201
     * @return static|null
3202
     * @throws exception\DbException
3203
     */
3204
    public function getOrFail($data, $with = [], $cache = false)
3205
    {
3206
        return $this->get($data, $with, $cache, true);
3207
    }
3208
3209
    /**
3210
     * 查找所有记录
3211
     * @access public
3212
     * @param  mixed        $data  主键列表或者查询条件(闭包)
3213
     * @param  array|string $with  关联预查询
3214
     * @param  bool         $cache 是否缓存
3215
     * @return static[]|false
3216
     * @throws exception\DbException
3217
     */
3218
    public function all($data = null, $with = [], $cache = false)
3219
    {
3220
        if (true === $with || is_int($with)) {
0 ignored issues
show
introduced by
The condition is_int($with) is always false.
Loading history...
3221
            $cache = $with;
3222
            $with  = [];
3223
        }
3224
3225
        return $this->parseQuery($data, $with, $cache)->select($data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parseQuery... $cache)->select($data) also could return the type PDOStatement|string|think\Collection which is incompatible with the documented return type array<mixed,think\db\Query>|false.
Loading history...
Bug introduced by
It seems like $with can also be of type array; however, parameter $with of think\db\Query::parseQuery() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

3225
        return $this->parseQuery($data, /** @scrutinizer ignore-type */ $with, $cache)->select($data);
Loading history...
3226
    }
3227
3228
    /**
3229
     * 分析查询表达式
3230
     * @access public
3231
     * @param  mixed  $data  主键列表或者查询条件(闭包)
3232
     * @param  string $with  关联预查询
3233
     * @param  bool   $cache 是否缓存
3234
     * @return Query
3235
     */
3236
    protected function parseQuery(&$data, $with, $cache)
3237
    {
3238
        $result = $this->with($with)->cache($cache);
3239
3240
        if ((is_array($data) && key($data) !== 0) || $data instanceof Where) {
3241
            $result = $result->where($data);
3242
            $data   = null;
3243
        } elseif ($data instanceof \Closure) {
3244
            $data($result);
3245
            $data = null;
3246
        } elseif ($data instanceof Query) {
3247
            $result = $data->with($with)->cache($cache);
3248
            $data   = null;
3249
        }
3250
3251
        return $result;
3252
    }
3253
3254
    /**
3255
     * 处理数据
3256
     * @access protected
3257
     * @param  array $result     查询数据
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
3258
     * @return void
3259
     */
3260
    protected function result(&$result)
3261
    {
3262
        if (!empty($this->options['json'])) {
3263
            $this->jsonResult($result, $this->options['json'], true);
3264
        }
3265
3266
        if (!empty($this->options['with_attr'])) {
3267
            $this->getResultAttr($result, $this->options['with_attr']);
3268
        }
3269
    }
3270
3271
    /**
3272
     * 使用获取器处理数据
3273
     * @access protected
3274
     * @param  array $result     查询数据
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 5 found
Loading history...
3275
     * @param  array $withAttr   字段获取器
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
3276
     * @return void
3277
     */
3278
    protected function getResultAttr(&$result, $withAttr = [])
3279
    {
3280
        foreach ($withAttr as $name => $closure) {
3281
            $name = Loader::parseName($name);
3282
3283
            if (strpos($name, '.')) {
3284
                // 支持JSON字段 获取器定义
3285
                list($key, $field) = explode('.', $name);
3286
3287
                if (isset($result[$key])) {
3288
                    $result[$key][$field] = $closure(isset($result[$key][$field]) ? $result[$key][$field] : null, $result[$key]);
3289
                }
3290
            } else {
3291
                $result[$name] = $closure(isset($result[$name]) ? $result[$name] : null, $result);
3292
            }
3293
        }
3294
    }
3295
3296
    /**
3297
     * JSON字段数据转换
3298
     * @access protected
3299
     * @param  array $result            查询数据
0 ignored issues
show
Coding Style introduced by
Expected 11 spaces after parameter name; 12 found
Loading history...
3300
     * @param  array $json              JSON字段
0 ignored issues
show
Coding Style introduced by
Expected 13 spaces after parameter name; 14 found
Loading history...
3301
     * @param  bool  $assoc             是否转换为数组
0 ignored issues
show
Coding Style introduced by
Expected 12 spaces after parameter name; 13 found
Loading history...
3302
     * @param  array $withRelationAttr  关联获取器
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
3303
     * @return void
3304
     */
3305
    protected function jsonResult(&$result, $json = [], $assoc = false, $withRelationAttr = [])
3306
    {
3307
        foreach ($json as $name) {
3308
            if (isset($result[$name])) {
3309
                $result[$name] = json_decode($result[$name], $assoc);
3310
3311
                if (isset($withRelationAttr[$name])) {
3312
                    foreach ($withRelationAttr[$name] as $key => $closure) {
3313
                        $data                = get_object_vars($result[$name]);
3314
                        $result[$name]->$key = $closure(isset($result[$name]->$key) ? $result[$name]->$key : null, $data);
3315
                    }
3316
                }
3317
            }
3318
        }
3319
    }
3320
3321
    /**
3322
     * 查询数据转换为模型对象
3323
     * @access protected
3324
     * @param  array $result            查询数据
0 ignored issues
show
Coding Style introduced by
Expected 11 spaces after parameter name; 12 found
Loading history...
3325
     * @param  array $options           查询参数
0 ignored issues
show
Coding Style introduced by
Expected 10 spaces after parameter name; 11 found
Loading history...
3326
     * @param  bool  $resultSet         是否为数据集查询
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter name; 9 found
Loading history...
3327
     * @param  array $withRelationAttr  关联字段获取器
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 2 found
Loading history...
3328
     * @return void
3329
     */
3330
    protected function resultToModel(&$result, $options = [], $resultSet = false, $withRelationAttr = [])
3331
    {
3332
        // 动态获取器
3333
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
3334
            foreach ($options['with_attr'] as $name => $val) {
3335
                if (strpos($name, '.')) {
3336
                    list($relation, $field) = explode('.', $name);
3337
3338
                    $withRelationAttr[$relation][$field] = $val;
3339
                    unset($options['with_attr'][$name]);
3340
                }
3341
            }
3342
        }
3343
3344
        // JSON 数据处理
3345
        if (!empty($options['json'])) {
3346
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
3347
        }
3348
3349
        $result = $this->model->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options));
3350
3351
        // 动态获取器
3352
        if (!empty($options['with_attr'])) {
3353
            $result->withAttribute($options['with_attr']);
3354
        }
3355
3356
        // 输出属性控制
3357
        if (!empty($options['visible'])) {
3358
            $result->visible($options['visible']);
3359
        } elseif (!empty($options['hidden'])) {
3360
            $result->hidden($options['hidden']);
3361
        }
3362
3363
        if (!empty($options['append'])) {
3364
            $result->append($options['append']);
3365
        }
3366
3367
        // 关联查询
3368
        if (!empty($options['relation'])) {
3369
            $result->relationQuery($options['relation'], $withRelationAttr);
3370
        }
3371
3372
        // 预载入查询
3373
        if (!$resultSet && !empty($options['with'])) {
3374
            $result->eagerlyResult($result, $options['with'], $withRelationAttr);
3375
        }
3376
3377
        // JOIN预载入查询
3378
        if (!$resultSet && !empty($options['with_join'])) {
3379
            $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true);
3380
        }
3381
3382
        // 关联统计
3383
        if (!empty($options['with_count'])) {
3384
            foreach ($options['with_count'] as $val) {
3385
                $result->relationCount($result, $val[0], $val[1], $val[2]);
3386
            }
3387
        }
3388
    }
3389
3390
    /**
3391
     * 获取模型的更新条件
3392
     * @access protected
3393
     * @param  array $options 查询参数
3394
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
3395
    protected function getModelUpdateCondition(array $options)
3396
    {
3397
        return isset($options['where']['AND']) ? $options['where']['AND'] : null;
3398
    }
3399
3400
    /**
3401
     * 查询失败 抛出异常
3402
     * @access protected
3403
     * @param  array $options 查询参数
3404
     * @throws ModelNotFoundException
3405
     * @throws DataNotFoundException
3406
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
3407
    protected function throwNotFound($options = [])
3408
    {
3409
        if (!empty($this->model)) {
3410
            $class = get_class($this->model);
3411
            throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options);
3412
        }
3413
        $table = is_array($options['table']) ? key($options['table']) : $options['table'];
3414
        throw new DataNotFoundException('table data not Found:' . $table, $table, $options);
3415
    }
3416
3417
    /**
3418
     * 查找多条记录 如果不存在则抛出异常
3419
     * @access public
3420
     * @param  array|string|Query|\Closure $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
3421
     * @return array|\PDOStatement|string|Model
3422
     * @throws DbException
3423
     * @throws ModelNotFoundException
3424
     * @throws DataNotFoundException
3425
     */
3426
    public function selectOrFail($data = null)
3427
    {
3428
        return $this->failException(true)->select($data);
3429
    }
3430
3431
    /**
3432
     * 查找单条记录 如果不存在则抛出异常
3433
     * @access public
3434
     * @param  array|string|Query|\Closure $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
3435
     * @return array|\PDOStatement|string|Model
3436
     * @throws DbException
3437
     * @throws ModelNotFoundException
3438
     * @throws DataNotFoundException
3439
     */
3440
    public function findOrFail($data = null)
3441
    {
3442
        return $this->failException(true)->find($data);
3443
    }
3444
3445
    /**
3446
     * 查找单条记录 如果不存在则抛出异常
3447
     * @access public
3448
     * @param  array|string|Query|\Closure $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
3449
     * @return array|\PDOStatement|string|Model
3450
     * @throws DbException
3451
     * @throws ModelNotFoundException
3452
     * @throws DataNotFoundException
3453
     */
3454
    public function findOrEmpty($data = null)
3455
    {
3456
        return $this->allowEmpty(true)->find($data);
3457
    }
3458
3459
    /**
3460
     * 分批数据返回处理
3461
     * @access public
3462
     * @param  integer      $count    每次处理的数据数量
3463
     * @param  callable     $callback 处理回调方法
3464
     * @param  string|array $column   分批处理的字段名
3465
     * @param  string       $order    字段排序
3466
     * @return boolean
3467
     * @throws DbException
3468
     */
3469
    public function chunk($count, $callback, $column = null, $order = 'asc')
3470
    {
3471
        $options = $this->getOptions();
3472
        $column  = $column ?: $this->getPk($options);
3473
3474
        if (isset($options['order'])) {
3475
            if (Container::get('app')->isDebug()) {
3476
                throw new DbException('chunk not support call order');
3477
            }
3478
            unset($options['order']);
3479
        }
3480
3481
        $bind = $this->bind;
3482
3483
        if (is_array($column)) {
3484
            $times = 1;
3485
            $query = $this->options($options)->page($times, $count);
3486
        } else {
3487
            $query = $this->options($options)->limit($count);
3488
3489
            if (strpos($column, '.')) {
3490
                list($alias, $key) = explode('.', $column);
3491
            } else {
3492
                $key = $column;
3493
            }
3494
        }
3495
3496
        $resultSet = $query->order($column, $order)->select();
3497
3498
        while (count($resultSet) > 0) {
3499
            if ($resultSet instanceof Collection) {
3500
                $resultSet = $resultSet->all();
3501
            }
3502
3503
            if (false === call_user_func($callback, $resultSet)) {
3504
                return false;
3505
            }
3506
3507
            if (isset($times)) {
3508
                $times++;
3509
                $query = $this->options($options)->page($times, $count);
3510
            } else {
3511
                $end    = end($resultSet);
3512
                $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...
3513
3514
                $query = $this->options($options)
3515
                    ->limit($count)
3516
                    ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
3517
            }
3518
3519
            $resultSet = $query->bind($bind)->order($column, $order)->select();
3520
        }
3521
3522
        return true;
3523
    }
3524
3525
    /**
3526
     * 获取绑定的参数 并清空
3527
     * @access public
3528
     * @param  bool $clear
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
3529
     * @return array
3530
     */
3531
    public function getBind($clear = true)
3532
    {
3533
        $bind = $this->bind;
3534
        if ($clear) {
3535
            $this->bind = [];
3536
        }
3537
3538
        return $bind;
3539
    }
3540
3541
    /**
3542
     * 创建子查询SQL
3543
     * @access public
3544
     * @param  bool $sub
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
3545
     * @return string
3546
     * @throws DbException
3547
     */
3548
    public function buildSql($sub = true)
3549
    {
3550
        return $sub ? '( ' . $this->select(false) . ' )' : $this->select(false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sub ? '( ' . $th... : $this->select(false) also could return the type PDOStatement|array which is incompatible with the documented return type string.
Loading history...
3551
    }
3552
3553
    /**
3554
     * 视图查询处理
3555
     * @access protected
3556
     * @param  array   $options    查询参数
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
3557
     * @return void
3558
     */
3559
    protected function parseView(&$options)
3560
    {
3561
        if (!isset($options['map'])) {
3562
            return;
3563
        }
3564
3565
        foreach (['AND', 'OR'] as $logic) {
3566
            if (isset($options['where'][$logic])) {
3567
                foreach ($options['where'][$logic] as $key => $val) {
3568
                    if (array_key_exists($key, $options['map'])) {
3569
                        array_shift($val);
3570
                        array_unshift($val, $options['map'][$key]);
3571
                        $options['where'][$logic][$options['map'][$key]] = $val;
3572
                        unset($options['where'][$logic][$key]);
3573
                    }
3574
                }
3575
            }
3576
        }
3577
3578
        if (isset($options['order'])) {
3579
            // 视图查询排序处理
3580
            if (is_string($options['order'])) {
3581
                $options['order'] = explode(',', $options['order']);
3582
            }
3583
            foreach ($options['order'] as $key => $val) {
3584
                if (is_numeric($key) && is_string($val)) {
3585
                    if (strpos($val, ' ')) {
3586
                        list($field, $sort) = explode(' ', $val);
3587
                        if (array_key_exists($field, $options['map'])) {
3588
                            $options['order'][$options['map'][$field]] = $sort;
3589
                            unset($options['order'][$key]);
3590
                        }
3591
                    } elseif (array_key_exists($val, $options['map'])) {
3592
                        $options['order'][$options['map'][$val]] = 'asc';
3593
                        unset($options['order'][$key]);
3594
                    }
3595
                } elseif (array_key_exists($key, $options['map'])) {
3596
                    $options['order'][$options['map'][$key]] = $val;
3597
                    unset($options['order'][$key]);
3598
                }
3599
            }
3600
        }
3601
    }
3602
3603
    /**
3604
     * 把主键值转换为查询条件 支持复合主键
3605
     * @access public
3606
     * @param  array|string $data    主键数据
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
3607
     * @return void
3608
     * @throws Exception
3609
     */
3610
    public function parsePkWhere($data)
3611
    {
3612
        $pk = $this->getPk($this->options);
3613
        // 获取当前数据表
3614
        $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
3615
3616
        if (!empty($this->options['alias'][$table])) {
3617
            $alias = $this->options['alias'][$table];
3618
        }
3619
3620
        if (is_string($pk)) {
3621
            $key = isset($alias) ? $alias . '.' . $pk : $pk;
3622
            // 根据主键查询
3623
            if (is_array($data)) {
3624
                $where[$pk] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$where was never initialized. Although not strictly required by PHP, it is generally a good practice to add $where = array(); before regardless.
Loading history...
3625
            } else {
3626
                $where[$pk] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data];
3627
            }
3628
        } elseif (is_array($pk) && is_array($data) && !empty($data)) {
3629
            // 根据复合主键查询
3630
            foreach ($pk as $key) {
3631
                if (isset($data[$key])) {
3632
                    $attr        = isset($alias) ? $alias . '.' . $key : $key;
3633
                    $where[$key] = [$attr, '=', $data[$key]];
3634
                } else {
3635
                    throw new Exception('miss complex primary data');
3636
                }
3637
            }
3638
        }
3639
3640
        if (!empty($where)) {
3641
            if (isset($this->options['where']['AND'])) {
3642
                $this->options['where']['AND'] = array_merge($this->options['where']['AND'], $where);
3643
            } else {
3644
                $this->options['where']['AND'] = $where;
3645
            }
3646
        }
3647
3648
        return;
3649
    }
3650
3651
    /**
3652
     * 分析表达式(可用于查询或者写入操作)
3653
     * @access protected
3654
     * @return array
3655
     */
3656
    protected function parseOptions()
3657
    {
3658
        $options = $this->getOptions();
3659
3660
        // 获取数据表
3661
        if (empty($options['table'])) {
3662
            $options['table'] = $this->getTable();
3663
        }
3664
3665
        if (!isset($options['where'])) {
3666
            $options['where'] = [];
3667
        } elseif (isset($options['view'])) {
3668
            // 视图查询条件处理
3669
            $this->parseView($options);
3670
        }
3671
3672
        if (!isset($options['field'])) {
3673
            $options['field'] = '*';
3674
        }
3675
3676
        foreach (['data', 'order', 'join', 'union'] as $name) {
3677
            if (!isset($options[$name])) {
3678
                $options[$name] = [];
3679
            }
3680
        }
3681
3682
        if (!isset($options['strict'])) {
3683
            $options['strict'] = $this->getConfig('fields_strict');
3684
        }
3685
3686
        foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) {
3687
            if (!isset($options[$name])) {
3688
                $options[$name] = false;
3689
            }
3690
        }
3691
3692
        if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) {
3693
            $options['master'] = true;
3694
        }
3695
3696
        foreach (['group', 'having', 'limit', 'force', 'comment'] as $name) {
3697
            if (!isset($options[$name])) {
3698
                $options[$name] = '';
3699
            }
3700
        }
3701
3702
        if (isset($options['page'])) {
3703
            // 根据页数计算limit
3704
            list($page, $listRows) = $options['page'];
3705
            $page                  = $page > 0 ? $page : 1;
3706
            $listRows              = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
3707
            $offset                = $listRows * ($page - 1);
3708
            $options['limit']      = $offset . ',' . $listRows;
3709
        }
3710
3711
        $this->options = $options;
3712
3713
        return $options;
3714
    }
3715
3716
    /**
3717
     * 注册回调方法
3718
     * @access public
3719
     * @param  string   $event    事件名
3720
     * @param  callable $callback 回调方法
3721
     * @return void
3722
     */
3723
    public static function event($event, $callback)
3724
    {
3725
        self::$event[$event] = $callback;
3726
    }
3727
3728
    /**
3729
     * 触发事件
3730
     * @access public
3731
     * @param  string $event   事件名
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
3732
     * @return bool
3733
     */
3734
    public function trigger($event)
3735
    {
3736
        $result = false;
3737
3738
        if (isset(self::$event[$event])) {
3739
            $result = Container::getInstance()->invoke(self::$event[$event], [$this]);
3740
        }
3741
3742
        return $result;
3743
    }
3744
3745
}
3746