Passed
Push — 5.1 ( 270c87...eb3948 )
by liu
12:54 queued 05:01
created

Model::isExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~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;
13
14
use InvalidArgumentException;
15
use think\db\Query;
16
17
/**
18
 * Class Model
19
 * @package think
0 ignored issues
show
Coding Style introduced by
Package name "think" is not valid; consider "Think" instead
Loading history...
20
 * @mixin Query
21
 * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件
22
 * @method Query whereRaw(string $where, array $bind = []) static 表达式查询
23
 * @method Query whereExp(string $field, string $condition, array $bind = []) static 字段表达式查询
24
 * @method Query when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询
25
 * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询
26
 * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询
27
 * @method Query with(mixed $with) static 关联预载入
28
 * @method Query count(string $field) static Count统计查询
29
 * @method Query min(string $field) static Min统计查询
30
 * @method Query max(string $field) static Max统计查询
31
 * @method Query sum(string $field) static SUM统计查询
32
 * @method Query avg(string $field) static Avg统计查询
33
 * @method Query field(mixed $field, boolean $except = false) static 指定查询字段
34
 * @method Query fieldRaw(string $field, array $bind = []) static 指定查询字段
35
 * @method Query union(mixed $union, boolean $all = false) static UNION查询
36
 * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT
37
 * @method Query order(mixed $field, string $order = null) static 查询ORDER
38
 * @method Query orderRaw(string $field, array $bind = []) static 查询ORDER
39
 * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存
40
 * @method mixed value(string $field) static 获取某个字段的值
41
 * @method array column(string $field, string $key = '') static 获取某个列的值
42
 * @method mixed find(mixed $data = null) static 查询单个记录
43
 * @method mixed select(mixed $data = null) static 查询多个记录
44
 * @method mixed get(mixed $data = null,mixed $with =[],bool $cache= false) static 查询单个记录 支持关联预载入
45
 * @method mixed getOrFail(mixed $data = null,mixed $with =[],bool $cache= false) static 查询单个记录 不存在则抛出异常
46
 * @method mixed findOrEmpty(mixed $data = null,mixed $with =[],bool $cache= false) static 查询单个记录  不存在则返回空模型
47
 * @method mixed all(mixed $data = null,mixed $with =[],bool $cache= false) static 查询多个记录 支持关联预载入
48
 * @method \think\Model withAttr(array $name,\Closure $closure) 动态定义获取器
49
 */
50
abstract class Model implements \JsonSerializable, \ArrayAccess
51
{
52
    use model\concern\Attribute;
53
    use model\concern\RelationShip;
54
    use model\concern\ModelEvent;
55
    use model\concern\TimeStamp;
56
    use model\concern\Conversion;
57
58
    /**
59
     * 是否存在数据
60
     * @var bool
61
     */
62
    private $exists = false;
0 ignored issues
show
Coding Style introduced by
Private member variable "exists" must be prefixed with an underscore
Loading history...
63
64
    /**
65
     * 是否Replace
66
     * @var bool
67
     */
68
    private $replace = false;
0 ignored issues
show
Coding Style introduced by
Private member variable "replace" must be prefixed with an underscore
Loading history...
69
70
    /**
71
     * 是否强制更新所有数据
72
     * @var bool
73
     */
74
    private $force = false;
0 ignored issues
show
Coding Style introduced by
Private member variable "force" must be prefixed with an underscore
Loading history...
75
76
    /**
77
     * 更新条件
78
     * @var array
79
     */
80
    private $updateWhere;
0 ignored issues
show
Coding Style introduced by
Private member variable "updateWhere" must be prefixed with an underscore
Loading history...
81
82
    /**
83
     * 数据库配置信息
84
     * @var array|string
85
     */
86
    protected $connection = [];
87
88
    /**
89
     * 数据库查询对象类名
90
     * @var string
91
     */
92
    protected $query;
93
94
    /**
95
     * 模型名称
96
     * @var string
97
     */
98
    protected $name;
99
100
    /**
101
     * 数据表名称
102
     * @var string
103
     */
104
    protected $table;
105
106
    /**
107
     * 写入自动完成定义
108
     * @var array
109
     */
110
    protected $auto = [];
111
112
    /**
113
     * 新增自动完成定义
114
     * @var array
115
     */
116
    protected $insert = [];
117
118
    /**
119
     * 更新自动完成定义
120
     * @var array
121
     */
122
    protected $update = [];
123
124
    /**
125
     * 初始化过的模型.
126
     * @var array
127
     */
128
    protected static $initialized = [];
129
130
    /**
131
     * 是否从主库读取(主从分布式有效)
132
     * @var array
133
     */
134
    protected static $readMaster;
135
136
    /**
137
     * 查询对象实例
138
     * @var Query
139
     */
140
    protected $queryInstance;
141
142
    /**
143
     * 错误信息
144
     * @var mixed
145
     */
146
    protected $error;
147
148
    /**
149
     * 软删除字段默认值
150
     * @var mixed
151
     */
152
    protected $defaultSoftDelete;
153
154
    /**
155
     * 全局查询范围
156
     * @var array
157
     */
158
    protected $globalScope = [];
159
160
    /**
161
     * 架构函数
162
     * @access public
163
     * @param  array|object $data 数据
164
     */
165
    public function __construct($data = [])
166
    {
167
        if (is_object($data)) {
168
            $this->data = get_object_vars($data);
169
        } else {
170
            $this->data = $data;
171
        }
172
173
        if ($this->disuse) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->disuse 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...
174
            // 废弃字段
175
            foreach ((array) $this->disuse as $key) {
176
                if (array_key_exists($key, $this->data)) {
177
                    unset($this->data[$key]);
178
                }
179
            }
180
        }
181
182
        // 记录原始数据
183
        $this->origin = $this->data;
184
185
        $config = Db::getConfig();
186
187
        if (empty($this->name)) {
188
            // 当前模型名
189
            $name       = str_replace('\\', '/', static::class);
190
            $this->name = basename($name);
191
            if (Container::get('config')->get('class_suffix')) {
192
                $suffix     = basename(dirname($name));
193
                $this->name = substr($this->name, 0, -strlen($suffix));
194
            }
195
        }
196
197
        if (is_null($this->autoWriteTimestamp)) {
0 ignored issues
show
introduced by
The condition is_null($this->autoWriteTimestamp) is always false.
Loading history...
198
            // 自动写入时间戳
199
            $this->autoWriteTimestamp = $config['auto_timestamp'];
200
        }
201
202
        if (is_null($this->dateFormat)) {
0 ignored issues
show
introduced by
The condition is_null($this->dateFormat) is always false.
Loading history...
203
            // 设置时间戳格式
204
            $this->dateFormat = $config['datetime_format'];
205
        }
206
207
        if (is_null($this->resultSetType)) {
0 ignored issues
show
introduced by
The condition is_null($this->resultSetType) is always false.
Loading history...
208
            $this->resultSetType = $config['resultset_type'];
209
        }
210
211
        if (!empty($this->connection) && is_array($this->connection)) {
212
            // 设置模型的数据库连接
213
            $this->connection = array_merge($config, $this->connection);
214
        }
215
216
        if ($this->observerClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->observerClass 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...
217
            // 注册模型观察者
218
            static::observe($this->observerClass);
0 ignored issues
show
Bug introduced by
$this->observerClass of type array is incompatible with the type object|string expected by parameter $class of think\Model::observe(). ( Ignorable by Annotation )

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

218
            static::observe(/** @scrutinizer ignore-type */ $this->observerClass);
Loading history...
219
        }
220
221
        // 执行初始化操作
222
        $this->initialize();
223
    }
224
225
    /**
226
     * 获取当前模型名称
227
     * @access public
228
     * @return string
229
     */
230
    public function getName()
231
    {
232
        return $this->name;
233
    }
234
235
    /**
236
     * 是否从主库读取数据(主从分布有效)
237
     * @access public
238
     * @param  bool     $all 是否所有模型有效
239
     * @return $this
240
     */
241
    public function readMaster($all = false)
242
    {
243
        $model = $all ? '*' : static::class;
244
245
        static::$readMaster[$model] = true;
246
247
        return $this;
248
    }
249
250
    /**
251
     * 创建新的模型实例
252
     * @access public
253
     * @param  array|object $data 数据
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
254
     * @param  bool         $isUpdate 是否为更新
255
     * @param  mixed        $where 更新条件
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
256
     * @return Model
257
     */
258
    public function newInstance($data = [], $isUpdate = false, $where = null)
259
    {
260
        return (new static($data))->isUpdate($isUpdate, $where);
261
    }
262
263
    /**
264
     * 创建模型的查询对象
265
     * @access protected
266
     * @return Query
267
     */
268
    protected function buildQuery()
269
    {
270
        // 设置当前模型 确保查询返回模型对象
271
        $query = Db::connect($this->connection, false, $this->query);
272
        $query->model($this)
273
            ->name($this->name)
274
            ->json($this->json, $this->jsonAssoc)
275
            ->setJsonFieldType($this->jsonType);
276
277
        if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) {
278
            $query->master(true);
279
        }
280
281
        // 设置当前数据表和模型名
282
        if (!empty($this->table)) {
283
            $query->table($this->table);
284
        }
285
286
        if (!empty($this->pk)) {
287
            $query->pk($this->pk);
288
        }
289
290
        return $query;
291
    }
292
293
    /**
294
     * 获取当前模型的数据库查询对象
295
     * @access public
296
     * @param  Query $query 查询对象实例
297
     * @return $this
298
     */
299
    public function setQuery($query)
300
    {
301
        $this->queryInstance = $query;
302
        return $this;
303
    }
304
305
    /**
306
     * 获取当前模型的数据库查询对象
307
     * @access public
308
     * @param  bool|array $useBaseQuery 是否调用全局查询范围(或者指定查询范围名称)
309
     * @return Query
310
     */
311
    public function db($useBaseQuery = true)
312
    {
313
        if ($this->queryInstance) {
314
            return $this->queryInstance;
315
        }
316
317
        $query = $this->buildQuery();
318
319
        // 软删除
320
        if (property_exists($this, 'withTrashed') && !$this->withTrashed) {
0 ignored issues
show
Bug Best Practice introduced by
The property withTrashed does not exist on think\Model. Since you implemented __get, consider adding a @property annotation.
Loading history...
321
            $this->withNoTrashed($query);
0 ignored issues
show
Bug introduced by
The method withNoTrashed() does not exist on think\Model. 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

321
            $this->/** @scrutinizer ignore-call */ 
322
                   withNoTrashed($query);
Loading history...
322
        }
323
324
        // 全局作用域
325
        if (true === $useBaseQuery && method_exists($this, 'base')) {
326
            call_user_func_array([$this, 'base'], [ & $query]);
327
        }
328
329
        $globalScope = is_array($useBaseQuery) && $useBaseQuery ? $useBaseQuery : $this->globalScope;
0 ignored issues
show
Bug Best Practice introduced by
The expression $useBaseQuery 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...
330
331
        if ($globalScope && false !== $useBaseQuery) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $globalScope 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...
332
            $query->scope($globalScope);
333
        }
334
335
        // 返回当前模型的数据库查询对象
336
        return $query;
337
    }
338
339
    /**
340
     *  初始化模型
341
     * @access protected
342
     * @return void
343
     */
344
    protected function initialize()
345
    {
346
        if (!isset(static::$initialized[static::class])) {
347
            static::$initialized[static::class] = true;
348
            static::init();
349
        }
350
    }
351
352
    /**
353
     * 初始化处理
354
     * @access protected
355
     * @return void
356
     */
357
    protected static function init()
358
    {}
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
359
360
    /**
361
     * 数据自动完成
362
     * @access protected
363
     * @param  array $auto 要自动更新的字段列表
364
     * @return void
365
     */
366
    protected function autoCompleteData($auto = [])
367
    {
368
        foreach ($auto as $field => $value) {
369
            if (is_integer($field)) {
370
                $field = $value;
371
                $value = null;
372
            }
373
374
            if (!isset($this->data[$field])) {
375
                $default = null;
376
            } else {
377
                $default = $this->data[$field];
378
            }
379
380
            $this->setAttr($field, !is_null($value) ? $value : $default);
381
        }
382
    }
383
384
    /**
385
     * 更新是否强制写入数据 而不做比较
386
     * @access public
387
     * @param  bool $force
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
388
     * @return $this
389
     */
390
    public function force($force = true)
391
    {
392
        $this->force = $force;
393
        return $this;
394
    }
395
396
    /**
397
     * 判断force
398
     * @access public
399
     * @return bool
400
     */
401
    public function isForce()
402
    {
403
        return $this->force;
404
    }
405
406
    /**
407
     * 新增数据是否使用Replace
408
     * @access public
409
     * @param  bool $replace
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
410
     * @return $this
411
     */
412
    public function replace($replace = true)
413
    {
414
        $this->replace = $replace;
415
        return $this;
416
    }
417
418
    /**
419
     * 设置数据是否存在
420
     * @access public
421
     * @param  bool $exists
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
422
     * @return $this
423
     */
424
    public function exists($exists)
425
    {
426
        $this->exists = $exists;
427
        return $this;
428
    }
429
430
    /**
431
     * 判断数据是否存在数据库
432
     * @access public
433
     * @return bool
434
     */
435
    public function isExists()
436
    {
437
        return $this->exists;
438
    }
439
440
    /**
441
     * 判断模型是否为空
442
     * @access public
443
     * @return bool
444
     */
445
    public function isEmpty()
446
    {
447
        return empty($this->data);
448
    }
449
450
    /**
451
     * 保存当前数据对象
452
     * @access public
453
     * @param  array  $data     数据
454
     * @param  array  $where    更新条件
455
     * @param  string $sequence 自增序列名
456
     * @return bool
457
     */
458
    public function save($data = [], $where = [], $sequence = null)
459
    {
460
        if (is_string($data)) {
0 ignored issues
show
introduced by
The condition is_string($data) is always false.
Loading history...
461
            $sequence = $data;
462
            $data     = [];
463
        }
464
465
        if (!$this->checkBeforeSave($data, $where)) {
466
            return false;
467
        }
468
469
        $result = $this->exists ? $this->updateData($where) : $this->insertData($sequence);
470
471
        if (false === $result) {
472
            return false;
473
        }
474
475
        // 写入回调
476
        $this->trigger('after_write');
477
478
        // 重新记录原始数据
479
        $this->origin = $this->data;
480
        $this->set    = [];
481
482
        return true;
483
    }
484
485
    /**
486
     * 写入之前检查数据
487
     * @access protected
488
     * @param  array   $data  数据
489
     * @param  array   $where 保存条件
490
     * @return bool
491
     */
492
    protected function checkBeforeSave($data, $where)
493
    {
494
        if (!empty($data)) {
495
            // 数据对象赋值
496
            foreach ($data as $key => $value) {
497
                $this->setAttr($key, $value, $data);
498
            }
499
500
            if (!empty($where)) {
501
                $this->exists      = true;
502
                $this->updateWhere = $where;
503
            }
504
        }
505
506
        // 数据自动完成
507
        $this->autoCompleteData($this->auto);
508
509
        // 事件回调
510
        if (false === $this->trigger('before_write')) {
511
            return false;
512
        }
513
514
        return true;
515
    }
516
517
    /**
518
     * 检查数据是否允许写入
519
     * @access protected
520
     * @param  array   $append 自动完成的字段列表
521
     * @return array
522
     */
523
    protected function checkAllowFields(array $append = [])
524
    {
525
        // 检测字段
526
        if (empty($this->field) || true === $this->field) {
527
            $query = $this->db(false);
528
            $table = $this->table ?: $query->getTable();
529
530
            $this->field = $query->getConnection()->getTableFields($table);
531
532
            $field = $this->field;
533
        } else {
534
            $field = array_merge($this->field, $append);
535
536
            if ($this->autoWriteTimestamp) {
537
                array_push($field, $this->createTime, $this->updateTime);
538
            }
539
        }
540
541
        if ($this->disuse) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->disuse 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...
542
            // 废弃字段
543
            $field = array_diff($field, (array) $this->disuse);
544
        }
545
546
        return $field;
547
    }
548
549
    /**
550
     * 更新写入数据
551
     * @access protected
552
     * @param  mixed   $where 更新条件
553
     * @return bool
554
     */
555
    protected function updateData($where)
556
    {
557
        // 自动更新
558
        $this->autoCompleteData($this->update);
559
560
        // 事件回调
561
        if (false === $this->trigger('before_update')) {
562
            return false;
563
        }
564
565
        // 获取有更新的数据
566
        $data = $this->getChangedData();
567
568
        if (empty($data)) {
569
            // 关联更新
570
            if (!empty($this->relationWrite)) {
571
                $this->autoRelationUpdate();
572
            }
573
574
            return true;
575
        } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) {
576
            // 自动写入更新时间
577
            $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
0 ignored issues
show
Bug introduced by
It seems like $this->updateTime can also be of type true; however, parameter $name of think\Model::autoWriteTimestamp() 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

577
            $data[$this->updateTime] = $this->autoWriteTimestamp(/** @scrutinizer ignore-type */ $this->updateTime);
Loading history...
578
579
            $this->data[$this->updateTime] = $data[$this->updateTime];
580
        }
581
582
        if (empty($where) && !empty($this->updateWhere)) {
583
            $where = $this->updateWhere;
584
        }
585
586
        // 检查允许字段
587
        $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->update));
588
589
        // 保留主键数据
590
        foreach ($this->data as $key => $val) {
591
            if ($this->isPk($key)) {
592
                $data[$key] = $val;
593
            }
594
        }
595
596
        $pk    = $this->getPk();
597
        $array = [];
598
599
        foreach ((array) $pk as $key) {
600
            if (isset($data[$key])) {
601
                $array[] = [$key, '=', $data[$key]];
602
                unset($data[$key]);
603
            }
604
        }
605
606
        if (!empty($array)) {
607
            $where = $array;
608
        }
609
610
        foreach ((array) $this->relationWrite as $name => $val) {
611
            if (is_array($val)) {
612
                foreach ($val as $key) {
613
                    if (isset($data[$key])) {
614
                        unset($data[$key]);
615
                    }
616
                }
617
            }
618
        }
619
620
        // 模型更新
621
        $db = $this->db(false);
622
        $db->startTrans();
623
624
        try {
625
            $db->where($where)
626
                ->strict(false)
627
                ->field($allowFields)
628
                ->update($data);
629
630
            // 关联更新
631
            if (!empty($this->relationWrite)) {
632
                $this->autoRelationUpdate();
633
            }
634
635
            $db->commit();
636
637
            // 更新回调
638
            $this->trigger('after_update');
639
640
            return true;
641
        } catch (\Exception $e) {
642
            $db->rollback();
643
            throw $e;
644
        }
645
    }
646
647
    /**
648
     * 新增写入数据
649
     * @access protected
650
     * @param  string   $sequence 自增序列名
651
     * @return bool
652
     */
653
    protected function insertData($sequence)
654
    {
655
        // 自动写入
656
        $this->autoCompleteData($this->insert);
657
658
        // 时间戳自动写入
659
        $this->checkTimeStampWrite();
660
661
        if (false === $this->trigger('before_insert')) {
662
            return false;
663
        }
664
665
        // 检查允许字段
666
        $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->insert));
667
668
        $db = $this->db(false);
669
        $db->startTrans();
670
671
        try {
672
            $result = $db->strict(false)
673
                ->field($allowFields)
674
                ->insert($this->data, $this->replace, false, $sequence);
675
676
            // 获取自动增长主键
677
            if ($result && $insertId = $db->getLastInsID($sequence)) {
678
                $pk = $this->getPk();
679
680
                foreach ((array) $pk as $key) {
681
                    if (!isset($this->data[$key]) || '' == $this->data[$key]) {
682
                        $this->data[$key] = $insertId;
683
                    }
684
                }
685
            }
686
687
            // 关联写入
688
            if (!empty($this->relationWrite)) {
689
                $this->autoRelationInsert();
690
            }
691
692
            $db->commit();
693
694
            // 标记为更新
695
            $this->exists = true;
696
697
            // 新增回调
698
            $this->trigger('after_insert');
699
700
            return true;
701
        } catch (\Exception $e) {
702
            $db->rollback();
703
            throw $e;
704
        }
705
    }
706
707
    /**
708
     * 字段值(延迟)增长
709
     * @access public
710
     * @param  string  $field    字段名
711
     * @param  integer $step     增长值
712
     * @param  integer $lazyTime 延时时间(s)
713
     * @return bool
714
     * @throws Exception
715
     */
716
    public function setInc($field, $step = 1, $lazyTime = 0)
717
    {
718
        // 读取更新条件
719
        $where = $this->getWhere();
720
721
        // 事件回调
722
        if (false === $this->trigger('before_update')) {
723
            return false;
724
        }
725
726
        $result = $this->db(false)
727
            ->where($where)
728
            ->setInc($field, $step, $lazyTime);
729
730
        if (true !== $result) {
731
            $this->data[$field] += $step;
732
        }
733
734
        // 更新回调
735
        $this->trigger('after_update');
736
737
        return true;
738
    }
739
740
    /**
741
     * 字段值(延迟)减少
742
     * @access public
743
     * @param  string  $field    字段名
744
     * @param  integer $step     减少值
745
     * @param  integer $lazyTime 延时时间(s)
746
     * @return bool
747
     * @throws Exception
748
     */
749
    public function setDec($field, $step = 1, $lazyTime = 0)
750
    {
751
        // 读取更新条件
752
        $where = $this->getWhere();
753
754
        // 事件回调
755
        if (false === $this->trigger('before_update')) {
756
            return false;
757
        }
758
759
        $result = $this->db(false)
760
            ->where($where)
761
            ->setDec($field, $step, $lazyTime);
762
763
        if (true !== $result) {
764
            $this->data[$field] -= $step;
765
        }
766
767
        // 更新回调
768
        $this->trigger('after_update');
769
770
        return true;
771
    }
772
773
    /**
774
     * 获取当前的更新条件
775
     * @access protected
776
     * @return mixed
777
     */
778
    protected function getWhere()
779
    {
780
        // 删除条件
781
        $pk = $this->getPk();
782
783
        $where = [];
784
        if (is_string($pk) && isset($this->data[$pk])) {
785
            $where[] = [$pk, '=', $this->data[$pk]];
786
        } elseif (is_array($pk)) {
787
            foreach ($pk as $field) {
788
                if (isset($this->data[$field])) {
789
                    $where[] = [$field, '=', $this->data[$field]];
790
                }
791
            }
792
        }
793
794
        if (empty($where)) {
795
            $where = empty($this->updateWhere) ? null : $this->updateWhere;
796
        }
797
798
        return $where;
799
    }
800
801
    /**
802
     * 保存多个数据到当前数据对象
803
     * @access public
804
     * @param  array   $dataSet 数据
805
     * @param  boolean $replace 是否自动识别更新和写入
806
     * @return Collection
807
     * @throws \Exception
808
     */
809
    public function saveAll($dataSet, $replace = true)
810
    {
811
        $db = $this->db(false);
812
        $db->startTrans();
813
814
        try {
815
            $pk = $this->getPk();
816
817
            if (is_string($pk) && $replace) {
818
                $auto = true;
819
            }
820
821
            $result = [];
822
823
            foreach ($dataSet as $key => $data) {
824
                if ($this->exists || (!empty($auto) && isset($data[$pk]))) {
825
                    $result[$key] = self::update($data, [], $this->field);
826
                } else {
827
                    $result[$key] = self::create($data, $this->field, $this->replace);
828
                }
829
            }
830
831
            $db->commit();
832
833
            return $this->toCollection($result);
834
        } catch (\Exception $e) {
835
            $db->rollback();
836
            throw $e;
837
        }
838
    }
839
840
    /**
841
     * 是否为更新数据
842
     * @access public
843
     * @param  mixed  $update
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
844
     * @param  mixed  $where
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
845
     * @return $this
846
     */
847
    public function isUpdate($update = true, $where = null)
848
    {
849
        if (is_bool($update)) {
850
            $this->exists = $update;
851
852
            if (!empty($where)) {
853
                $this->updateWhere = $where;
854
            }
855
        } else {
856
            $this->exists      = true;
857
            $this->updateWhere = $update;
858
        }
859
860
        return $this;
861
    }
862
863
    /**
864
     * 删除当前的记录
865
     * @access public
866
     * @return bool
867
     */
868
    public function delete()
869
    {
870
        if (!$this->exists || false === $this->trigger('before_delete')) {
871
            return false;
872
        }
873
874
        // 读取更新条件
875
        $where = $this->getWhere();
876
877
        $db = $this->db(false);
878
        $db->startTrans();
879
880
        try {
881
            // 删除当前模型数据
882
            $db->where($where)->delete();
883
884
            // 关联删除
885
            if (!empty($this->relationWrite)) {
886
                $this->autoRelationDelete();
887
            }
888
889
            $db->commit();
890
891
            $this->trigger('after_delete');
892
893
            $this->exists = false;
894
895
            return true;
896
        } catch (\Exception $e) {
897
            $db->rollback();
898
            throw $e;
899
        }
900
    }
901
902
    /**
903
     * 设置自动完成的字段( 规则通过修改器定义)
904
     * @access public
905
     * @param  array $fields 需要自动完成的字段
906
     * @return $this
907
     */
908
    public function auto($fields)
909
    {
910
        $this->auto = $fields;
911
912
        return $this;
913
    }
914
915
    /**
916
     * 写入数据
917
     * @access public
918
     * @param  array      $data  数据数组
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 2 found
Loading history...
919
     * @param  array|true $field 允许字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
920
     * @param  bool       $replace 使用Replace
921
     * @return static
922
     */
923
    public static function create($data = [], $field = null, $replace = false)
924
    {
925
        $model = new static();
926
927
        if (!empty($field)) {
928
            $model->allowField($field);
929
        }
930
931
        $model->isUpdate(false)->replace($replace)->save($data, []);
932
933
        return $model;
934
    }
935
936
    /**
937
     * 更新数据
938
     * @access public
939
     * @param  array      $data  数据数组
940
     * @param  array      $where 更新条件
941
     * @param  array|true $field 允许字段
942
     * @return static
943
     */
944
    public static function update($data = [], $where = [], $field = null)
945
    {
946
        $model = new static();
947
948
        if (!empty($field)) {
949
            $model->allowField($field);
950
        }
951
952
        $model->isUpdate(true)->save($data, $where);
953
954
        return $model;
955
    }
956
957
    /**
958
     * 删除记录
959
     * @access public
960
     * @param  mixed $data 主键列表 支持闭包查询条件
961
     * @return bool
962
     */
963
    public static function destroy($data)
964
    {
965
        if (empty($data) && 0 !== $data) {
966
            return false;
967
        }
968
969
        $model = new static();
970
971
        $query = $model->db();
972
973
        if (is_array($data) && key($data) !== 0) {
974
            $query->where($data);
975
            $data = null;
976
        } elseif ($data instanceof \Closure) {
977
            $data($query);
978
            $data = null;
979
        }
980
981
        $resultSet = $query->select($data);
982
983
        if ($resultSet) {
984
            foreach ($resultSet as $data) {
0 ignored issues
show
introduced by
$data is overwriting one of the parameters of this function.
Loading history...
985
                $data->delete();
986
            }
987
        }
988
989
        return true;
990
    }
991
992
    /**
993
     * 获取错误信息
994
     * @access public
995
     * @return mixed
996
     */
997
    public function getError()
998
    {
999
        return $this->error;
1000
    }
1001
1002
    /**
1003
     * 解序列化后处理
1004
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1005
    public function __wakeup()
1006
    {
1007
        $this->initialize();
1008
    }
1009
1010
    public function __debugInfo()
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
1011
    {
1012
        return [
1013
            'data'     => $this->data,
1014
            'relation' => $this->relation,
1015
        ];
1016
    }
1017
1018
    /**
1019
     * 修改器 设置数据对象的值
1020
     * @access public
1021
     * @param  string $name  名称
1022
     * @param  mixed  $value 值
1023
     * @return void
1024
     */
1025
    public function __set($name, $value)
1026
    {
1027
        $this->setAttr($name, $value);
1028
    }
1029
1030
    /**
1031
     * 获取器 获取数据对象的值
1032
     * @access public
1033
     * @param  string $name 名称
1034
     * @return mixed
1035
     */
1036
    public function __get($name)
1037
    {
1038
        return $this->getAttr($name);
1039
    }
1040
1041
    /**
1042
     * 检测数据对象的值
1043
     * @access public
1044
     * @param  string $name 名称
1045
     * @return boolean
1046
     */
1047
    public function __isset($name)
1048
    {
1049
        try {
1050
            return !is_null($this->getAttr($name));
1051
        } catch (InvalidArgumentException $e) {
1052
            return false;
1053
        }
1054
    }
1055
1056
    /**
1057
     * 销毁数据对象的值
1058
     * @access public
1059
     * @param  string $name 名称
1060
     * @return void
1061
     */
1062
    public function __unset($name)
1063
    {
1064
        unset($this->data[$name], $this->relation[$name]);
1065
    }
1066
1067
    // ArrayAccess
1068
    public function offsetSet($name, $value)
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
1069
    {
1070
        $this->setAttr($name, $value);
1071
    }
1072
1073
    public function offsetExists($name)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
1074
    {
1075
        return $this->__isset($name);
1076
    }
1077
1078
    public function offsetUnset($name)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
1079
    {
1080
        $this->__unset($name);
1081
    }
1082
1083
    public function offsetGet($name)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
1084
    {
1085
        return $this->getAttr($name);
1086
    }
1087
1088
    /**
1089
     * 设置是否使用全局查询范围
1090
     * @access public
1091
     * @param  bool|array $use 是否启用全局查询范围(或者用数组指定查询范围名称)
1092
     * @return Query
1093
     */
1094
    public static function useGlobalScope($use)
1095
    {
1096
        $model = new static();
1097
1098
        return $model->db($use);
1099
    }
1100
1101
    public function __call($method, $args)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
1102
    {
1103
        if ('withattr' == strtolower($method)) {
1104
            return call_user_func_array([$this, 'withAttribute'], $args);
1105
        }
1106
1107
        return call_user_func_array([$this->db(), $method], $args);
1108
    }
1109
1110
    public static function __callStatic($method, $args)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
1111
    {
1112
        $model = new static();
1113
1114
        return call_user_func_array([$model->db(), $method], $args);
1115
    }
1116
}
1117