Passed
Pull Request — 5.1 (#2134)
by
unknown
05:49
created

Model::setConnection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
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
     * 获取当前模型的数据库连接标识
293
     * @access public
294
     * @return string
295
     */
296
    public function getConnection()
297
    {
298
        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...
299
    }
300
301
    /**
302
     * 设置当前模型数据表的后缀
303
     * @access public
304
     * @param string $suffix 数据表后缀
305
     * @return $this
306
     */
307
    public function setSuffix($suffix)
308
    {
309
        $this->suffix = $suffix;
310
        return $this;
311
    }
312
313
    /**
314
     * 获取当前模型的数据表后缀
315
     * @access public
316
     * @return string
317
     */
318
    public function getSuffix()
319
    {
320
        return $this->suffix ? $this->suffix : '';
321
    }
322
323
    /**
324
     * 创建模型的查询对象
325
     * @access protected
326
     * @return Query
327
     */
328
    protected function buildQuery()
329
    {
330
        // 设置当前模型 确保查询返回模型对象
331
        $query = Db::connect($this->connection, false, $this->query);
332
        $query->model($this)
333
            ->name($this->name . $this->suffix)
334
            ->json($this->json, $this->jsonAssoc)
335
            ->setJsonFieldType($this->jsonType);
336
337
        if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) {
338
            $query->master(true);
339
        }
340
341
        // 设置当前数据表和模型名
342
        if (!empty($this->table)) {
343
            $query->table($this->table . $this->suffix);
344
        }
345
346
        if (!empty($this->pk)) {
347
            $query->pk($this->pk);
348
        }
349
350
        return $query;
351
    }
352
353
    /**
354
     * 获取当前模型的数据库查询对象
355
     * @access public
356
     * @param  Query $query 查询对象实例
357
     * @return $this
358
     */
359
    public function setQuery($query)
360
    {
361
        $this->queryInstance = $query;
362
        return $this;
363
    }
364
365
    /**
366
     * 获取当前模型的数据库查询对象
367
     * @access public
368
     * @param  bool|array $useBaseQuery 是否调用全局查询范围(或者指定查询范围名称)
369
     * @return Query
370
     */
371
    public function db($useBaseQuery = true)
372
    {
373
        if ($this->queryInstance) {
374
            return $this->queryInstance;
375
        }
376
377
        $query = $this->buildQuery();
378
379
        // 软删除
380
        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...
381
            $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

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

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