Passed
Pull Request — 5.1 (#2134)
by
unknown
07:00
created

Model::getSuffix()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 1
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 3
rs 10
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
     * @var string
163
     */
164
    protected $suffix;
165
    
166
    /**
167
     * 架构函数
168
     * @access public
169
     * @param  array|object $data 数据
170
     */
171
    public function __construct($data = [])
172
    {
173
        if (is_object($data)) {
174
            $this->data = get_object_vars($data);
175
        } else {
176
            $this->data = $data;
177
        }
178
179
        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...
180
            // 废弃字段
181
            foreach ((array) $this->disuse as $key) {
182
                if (array_key_exists($key, $this->data)) {
183
                    unset($this->data[$key]);
184
                }
185
            }
186
        }
187
188
        // 记录原始数据
189
        $this->origin = $this->data;
190
191
        $config = Db::getConfig();
192
193
        if (empty($this->name)) {
194
            // 当前模型名
195
            $name       = str_replace('\\', '/', static::class);
196
            $this->name = basename($name);
197
            if (Container::get('config')->get('class_suffix')) {
198
                $suffix     = basename(dirname($name));
199
                $this->name = substr($this->name, 0, -strlen($suffix));
200
            }
201
        }
202
203
        if (is_null($this->autoWriteTimestamp)) {
0 ignored issues
show
introduced by
The condition is_null($this->autoWriteTimestamp) is always false.
Loading history...
204
            // 自动写入时间戳
205
            $this->autoWriteTimestamp = $config['auto_timestamp'];
206
        }
207
208
        if (is_null($this->dateFormat)) {
0 ignored issues
show
introduced by
The condition is_null($this->dateFormat) is always false.
Loading history...
209
            // 设置时间戳格式
210
            $this->dateFormat = $config['datetime_format'];
211
        }
212
213
        if (is_null($this->resultSetType)) {
0 ignored issues
show
introduced by
The condition is_null($this->resultSetType) is always false.
Loading history...
214
            $this->resultSetType = $config['resultset_type'];
215
        }
216
217
        if (!empty($this->connection) && is_array($this->connection)) {
218
            // 设置模型的数据库连接
219
            $this->connection = array_merge($config, $this->connection);
220
        }
221
222
        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...
223
            // 注册模型观察者
224
            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

224
            static::observe(/** @scrutinizer ignore-type */ $this->observerClass);
Loading history...
225
        }
226
227
        // 执行初始化操作
228
        $this->initialize();
229
    }
230
231
    /**
232
     * 获取当前模型名称
233
     * @access public
234
     * @return string
235
     */
236
    public function getName()
237
    {
238
        return $this->name;
239
    }
240
241
    /**
242
     * 是否从主库读取数据(主从分布有效)
243
     * @access public
244
     * @param  bool     $all 是否所有模型有效
245
     * @return $this
246
     */
247
    public function readMaster($all = false)
248
    {
249
        $model = $all ? '*' : static::class;
250
251
        static::$readMaster[$model] = true;
252
253
        return $this;
254
    }
255
256
       /**
257
     * 创建新的模型实例
258
     * @access public
259
     * @param  array|object $data 数据
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
260
     * @param  bool         $isUpdate 是否为更新
261
     * @param  mixed        $where 更新条件
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
262
     * @return Model
263
     */
264
    public function newInstance($data = [], $isUpdate = false, $where = null)
265
    {
266
        $model = new static($data);
267
268
        if ($this->connection) {
269
            $model->setConnection($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type array; however, parameter $connection of think\Model::setConnection() 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

269
            $model->setConnection(/** @scrutinizer ignore-type */ $this->connection);
Loading history...
270
        }
271
272
        if ($this->suffix) {
273
            $model->setSuffix($this->suffix);
274
        }
275
276
        return $model->isUpdate($isUpdate, $where);
277
    }
278
279
    /**
280
     * 设置当前模型的数据库连接
281
     * @access public
282
     * @param string $connection 数据表连接标识
283
     * @return $this
284
     */
285
    public function setConnection($connection)
286
    {
287
        $this->connection = $connection;
288
        return $this;
289
    }
290
    /**
291
     * 获取当前模型的数据库连接标识
292
     * @access public
293
     * @return string
294
     */
295
    public function getConnection()
296
    {
297
        return $this->connection ? $this->connection : '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection ? $this->connection : '' also could return the type array which is incompatible with the documented return type string.
Loading history...
298
    }
299
300
    /**
301
     * 设置当前模型数据表的后缀
302
     * @access public
303
     * @param string $suffix 数据表后缀
304
     * @return $this
305
     */
306
    public function setSuffix($suffix)
307
    {
308
        $this->suffix = $suffix;
309
        return $this;
310
    }
311
312
    /**
313
     * 获取当前模型的数据表后缀
314
     * @access public
315
     * @return string
316
     */
317
    public function getSuffix()
318
    {
319
        return $this->suffix ? $this->suffix : '';
320
    }
321
322
    /**
323
     * 创建模型的查询对象
324
     * @access protected
325
     * @return Query
326
     */
327
    protected function buildQuery()
328
    {
329
        // 设置当前模型 确保查询返回模型对象
330
        $query = Db::connect($this->connection, false, $this->query);
331
        $query->model($this)
332
            ->name($this->name . $this->suffix)
333
            ->json($this->json, $this->jsonAssoc)
334
            ->setJsonFieldType($this->jsonType);
335
336
        if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) {
337
            $query->master(true);
338
        }
339
340
        // 设置当前数据表和模型名
341
        if (!empty($this->table)) {
342
            $query->table($this->table . $this->suffix);
343
        }
344
345
        if (!empty($this->pk)) {
346
            $query->pk($this->pk);
347
        }
348
349
        return $query;
350
    }
351
352
    /**
353
     * 获取当前模型的数据库查询对象
354
     * @access public
355
     * @param  Query $query 查询对象实例
356
     * @return $this
357
     */
358
    public function setQuery($query)
359
    {
360
        $this->queryInstance = $query;
361
        return $this;
362
    }
363
364
    /**
365
     * 获取当前模型的数据库查询对象
366
     * @access public
367
     * @param  bool|array $useBaseQuery 是否调用全局查询范围(或者指定查询范围名称)
368
     * @return Query
369
     */
370
    public function db($useBaseQuery = true)
371
    {
372
        if ($this->queryInstance) {
373
            return $this->queryInstance;
374
        }
375
376
        $query = $this->buildQuery();
377
378
        // 软删除
379
        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...
380
            $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

380
            $this->/** @scrutinizer ignore-call */ 
381
                   withNoTrashed($query);
Loading history...
381
        }
382
383
        // 全局作用域
384
        if (true === $useBaseQuery && method_exists($this, 'base')) {
385
            call_user_func_array([$this, 'base'], [ & $query]);
386
        }
387
388
        $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...
389
390
        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...
391
            $query->scope($globalScope);
392
        }
393
394
        // 返回当前模型的数据库查询对象
395
        return $query;
396
    }
397
398
    /**
399
     *  初始化模型
400
     * @access protected
401
     * @return void
402
     */
403
    protected function initialize()
404
    {
405
        if (!isset(static::$initialized[static::class])) {
406
            static::$initialized[static::class] = true;
407
            static::init();
408
        }
409
    }
410
411
    /**
412
     * 初始化处理
413
     * @access protected
414
     * @return void
415
     */
416
    protected static function init()
417
    {}
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
418
419
    /**
420
     * 数据自动完成
421
     * @access protected
422
     * @param  array $auto 要自动更新的字段列表
423
     * @return void
424
     */
425
    protected function autoCompleteData($auto = [])
426
    {
427
        foreach ($auto as $field => $value) {
428
            if (is_integer($field)) {
429
                $field = $value;
430
                $value = null;
431
            }
432
433
            if (!isset($this->data[$field])) {
434
                $default = null;
435
            } else {
436
                $default = $this->data[$field];
437
            }
438
439
            $this->setAttr($field, !is_null($value) ? $value : $default);
440
        }
441
    }
442
443
    /**
444
     * 更新是否强制写入数据 而不做比较
445
     * @access public
446
     * @param  bool $force
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
447
     * @return $this
448
     */
449
    public function force($force = true)
450
    {
451
        $this->force = $force;
452
        return $this;
453
    }
454
455
    /**
456
     * 判断force
457
     * @access public
458
     * @return bool
459
     */
460
    public function isForce()
461
    {
462
        return $this->force;
463
    }
464
465
    /**
466
     * 新增数据是否使用Replace
467
     * @access public
468
     * @param  bool $replace
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
469
     * @return $this
470
     */
471
    public function replace($replace = true)
472
    {
473
        $this->replace = $replace;
474
        return $this;
475
    }
476
477
    /**
478
     * 设置数据是否存在
479
     * @access public
480
     * @param  bool $exists
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
481
     * @return $this
482
     */
483
    public function exists($exists)
484
    {
485
        $this->exists = $exists;
486
        return $this;
487
    }
488
489
    /**
490
     * 判断数据是否存在数据库
491
     * @access public
492
     * @return bool
493
     */
494
    public function isExists()
495
    {
496
        return $this->exists;
497
    }
498
499
    /**
500
     * 判断模型是否为空
501
     * @access public
502
     * @return bool
503
     */
504
    public function isEmpty()
505
    {
506
        return empty($this->data);
507
    }
508
509
    /**
510
     * 保存当前数据对象
511
     * @access public
512
     * @param  array  $data     数据
513
     * @param  array  $where    更新条件
514
     * @param  string $sequence 自增序列名
515
     * @return bool
516
     */
517
    public function save($data = [], $where = [], $sequence = null)
518
    {
519
        if (is_string($data)) {
0 ignored issues
show
introduced by
The condition is_string($data) is always false.
Loading history...
520
            $sequence = $data;
521
            $data     = [];
522
        }
523
524
        if (!$this->checkBeforeSave($data, $where)) {
525
            return false;
526
        }
527
528
        $result = $this->exists ? $this->updateData($where) : $this->insertData($sequence);
529
530
        if (false === $result) {
531
            return false;
532
        }
533
534
        // 写入回调
535
        $this->trigger('after_write');
536
537
        // 重新记录原始数据
538
        $this->origin = $this->data;
539
        $this->set    = [];
540
541
        return true;
542
    }
543
544
    /**
545
     * 写入之前检查数据
546
     * @access protected
547
     * @param  array   $data  数据
548
     * @param  array   $where 保存条件
549
     * @return bool
550
     */
551
    protected function checkBeforeSave($data, $where)
552
    {
553
        if (!empty($data)) {
554
            // 数据对象赋值
555
            foreach ($data as $key => $value) {
556
                $this->setAttr($key, $value, $data);
557
            }
558
559
            if (!empty($where)) {
560
                $this->exists      = true;
561
                $this->updateWhere = $where;
562
            }
563
        }
564
565
        // 数据自动完成
566
        $this->autoCompleteData($this->auto);
567
568
        // 事件回调
569
        if (false === $this->trigger('before_write')) {
570
            return false;
571
        }
572
573
        return true;
574
    }
575
576
    /**
577
     * 检查数据是否允许写入
578
     * @access protected
579
     * @param  array   $append 自动完成的字段列表
580
     * @return array
581
     */
582
    protected function checkAllowFields(array $append = [])
583
    {
584
        // 检测字段
585
        if (empty($this->field) || true === $this->field) {
586
            $query = $this->db(false);
587
            $table = $this->table ?: $query->getTable();
588
589
            $this->field = $query->getConnection()->getTableFields($table);
590
591
            $field = $this->field;
592
        } else {
593
            $field = array_merge($this->field, $append);
594
595
            if ($this->autoWriteTimestamp) {
596
                array_push($field, $this->createTime, $this->updateTime);
597
            }
598
        }
599
600
        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...
601
            // 废弃字段
602
            $field = array_diff($field, (array) $this->disuse);
603
        }
604
605
        return $field;
606
    }
607
608
    /**
609
     * 更新写入数据
610
     * @access protected
611
     * @param  mixed   $where 更新条件
612
     * @return bool
613
     */
614
    protected function updateData($where)
615
    {
616
        // 自动更新
617
        $this->autoCompleteData($this->update);
618
619
        // 事件回调
620
        if (false === $this->trigger('before_update')) {
621
            return false;
622
        }
623
624
        // 获取有更新的数据
625
        $data = $this->getChangedData();
626
627
        if (empty($data)) {
628
            // 关联更新
629
            if (!empty($this->relationWrite)) {
630
                $this->autoRelationUpdate();
631
            }
632
633
            return true;
634
        } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) {
635
            // 自动写入更新时间
636
            $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

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