Passed
Push — 5.2 ( 5031d8...9b60ba )
by liu
02:28
created

RelationShip::morphTo()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 4
nop 2
dl 0
loc 18
rs 9.9332
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think\model\concern;
14
15
use think\App;
16
use think\Collection;
17
use think\db\Query;
18
use think\Model;
19
use think\model\Relation;
20
use think\model\relation\BelongsTo;
21
use think\model\relation\BelongsToMany;
22
use think\model\relation\HasMany;
23
use think\model\relation\HasManyThrough;
24
use think\model\relation\HasOne;
25
use think\model\relation\MorphMany;
26
use think\model\relation\MorphOne;
27
use think\model\relation\MorphTo;
28
29
/**
30
 * 模型关联处理
31
 */
32
trait RelationShip
33
{
34
    /**
35
     * 父关联模型对象
36
     * @var object
37
     */
38
    private $parent;
39
40
    /**
41
     * 模型关联数据
42
     * @var array
43
     */
44
    private $relation = [];
45
46
    /**
47
     * 关联写入定义信息
48
     * @var array
49
     */
50
    private $together = [];
51
52
    /**
53
     * 关联自动写入信息
54
     * @var array
55
     */
56
    protected $relationWrite = [];
57
58
    /**
59
     * 设置父关联对象
60
     * @access public
61
     * @param  Model $model  模型对象
62
     * @return $this
63
     */
64
    public function setParent(Model $model)
65
    {
66
        $this->parent = $model;
67
68
        return $this;
69
    }
70
71
    /**
72
     * 获取父关联对象
73
     * @access public
74
     * @return Model
75
     */
76
    public function getParent(): Model
77
    {
78
        return $this->parent;
79
    }
80
81
    /**
82
     * 获取当前模型的关联模型数据
83
     * @access public
84
     * @param  string $name 关联方法名
85
     * @return mixed
86
     */
87
    public function getRelation(string $name = null)
88
    {
89
        if (is_null($name)) {
90
            return $this->relation;
91
        }
92
93
        if (array_key_exists($name, $this->relation)) {
94
            return $this->relation[$name];
95
        }
96
    }
97
98
    /**
99
     * 设置关联数据对象值
100
     * @access public
101
     * @param  string $name  属性名
102
     * @param  mixed  $value 属性值
103
     * @param  array  $data  数据
104
     * @return $this
105
     */
106
    public function setRelation(string $name, $value, array $data = [])
107
    {
108
        // 检测修改器
109
        $method = 'set' . App::parseName($name, 1) . 'Attr';
110
111
        if (method_exists($this, $method)) {
112
            $value = $this->$method($value, array_merge($this->data, $data));
113
        }
114
115
        $this->relation[$name] = $value;
116
117
        return $this;
118
    }
119
120
    /**
121
     * 查询当前模型的关联数据
122
     * @access public
123
     * @param  array $relations 关联名
124
     * @return void
125
     */
126
    public function relationQuery(array $relations): void
127
    {
128
        foreach ($relations as $key => $relation) {
129
            $subRelation = '';
130
            $closure     = null;
131
132
            if ($relation instanceof \Closure) {
133
                // 支持闭包查询过滤关联条件
134
                $closure  = $relation;
135
                $relation = $key;
136
            }
137
138
            if (is_array($relation)) {
139
                $subRelation = $relation;
140
                $relation    = $key;
141
            } elseif (strpos($relation, '.')) {
142
                list($relation, $subRelation) = explode('.', $relation, 2);
143
            }
144
145
            $method = App::parseName($relation, 1, false);
146
147
            $this->relation[$relation] = $this->$method()->getRelation($subRelation, $closure);
148
        }
149
    }
150
151
    /**
152
     * 关联数据写入
153
     * @access public
154
     * @param  array $relation 关联
155
     * @return $this
156
     */
157
    public function together(array $relation)
158
    {
159
        $this->together = $relation;
160
161
        $this->checkAutoRelationWrite();
162
163
        return $this;
164
    }
165
166
    /**
167
     * 根据关联条件查询当前模型
168
     * @access public
169
     * @param  string  $relation 关联方法名
170
     * @param  mixed   $operator 比较操作符
171
     * @param  integer $count    个数
172
     * @param  string  $id       关联表的统计字段
173
     * @param  string  $joinType JOIN类型
174
     * @return Query
175
     */
176
    public static function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = ''): Query
177
    {
178
        return (new static())
179
            ->$relation()
180
            ->has($operator, $count, $id, $joinType);
181
    }
182
183
    /**
184
     * 根据关联条件查询当前模型
185
     * @access public
186
     * @param  string $relation 关联方法名
187
     * @param  mixed  $where    查询条件(数组或者闭包)
188
     * @param  mixed  $fields   字段
189
     * @param  string $joinType JOIN类型
190
     * @return Query
191
     */
192
    public static function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = ''): Query
193
    {
194
        return (new static())
195
            ->$relation()
196
            ->hasWhere($where, $fields, $joinType);
197
    }
198
199
    /**
200
     * 预载入关联查询 返回数据集
201
     * @access public
202
     * @param  array  $resultSet 数据集
203
     * @param  string $relation  关联名
204
     * @param  array  $withRelationAttr 关联获取器
205
     * @param  array  $withBind  关联绑定(一对一关联)
206
     * @param  bool   $join      是否为JOIN方式
207
     * @return void
208
     */
209
    public function eagerlyResultSet(array &$resultSet, array $relations, array $withRelationAttr = [], array $withBind = [], bool $join = false): void
210
    {
211
        foreach ($relations as $key => $relation) {
212
            $subRelation = [];
213
            $closure     = null;
214
215
            if ($relation instanceof \Closure) {
216
                $closure  = $relation;
217
                $relation = $key;
218
            }
219
220
            if (is_array($relation)) {
221
                $subRelation = $relation;
222
                $relation    = $key;
223
            } elseif (strpos($relation, '.')) {
224
                list($relation, $subRelation) = explode('.', $relation, 2);
225
226
                $subRelation = [$subRelation];
227
            }
228
229
            $relation     = App::parseName($relation, 1, false);
230
            $relationName = App::parseName($relation);
231
232
            $relationResult = $this->$relation();
233
234
            if (isset($withBind[$relationName])) {
235
                $relationResult->bind($withBind[$relationName]);
236
            }
237
238
            if (isset($withRelationAttr[$relationName])) {
239
                $relationResult->withAttr($withRelationAttr[$relationName]);
240
            }
241
242
            $relationResult->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $join);
243
        }
244
    }
245
246
    /**
247
     * 预载入关联查询 返回模型对象
248
     * @access public
249
     * @param  Model    $result    数据对象
250
     * @param  array    $relations 关联
251
     * @param  array    $withRelationAttr 关联获取器
252
     * @param  array    $withBind  关联绑定(一对一关联)
253
     * @param  bool     $join      是否为JOIN方式
254
     * @return void
255
     */
256
    public function eagerlyResult(Model $result, array $relations, array $withRelationAttr = [], array $withBind = [], bool $join = false): void
257
    {
258
        foreach ($relations as $key => $relation) {
259
            $subRelation = [];
260
            $closure     = null;
261
262
            if ($relation instanceof \Closure) {
263
                $closure  = $relation;
264
                $relation = $key;
265
            }
266
267
            if (is_array($relation)) {
268
                $subRelation = $relation;
269
                $relation    = $key;
270
            } elseif (strpos($relation, '.')) {
271
                list($relation, $subRelation) = explode('.', $relation, 2);
272
273
                $subRelation = [$subRelation];
274
            }
275
276
            $relation     = App::parseName($relation, 1, false);
277
            $relationName = App::parseName($relation);
278
279
            $relationResult = $this->$relation();
280
281
            if (isset($withBind[$relationName])) {
282
                $relationResult->bind($withBind[$relationName]);
283
            }
284
285
            if (isset($withRelationAttr[$relationName])) {
286
                $relationResult->withAttr($withRelationAttr[$relationName]);
287
            }
288
289
            $relationResult->eagerlyResult($result, $relation, $subRelation, $closure, $join);
290
        }
291
    }
292
293
    /**
294
     * 关联统计
295
     * @access public
296
     * @param  Model    $result     数据对象
297
     * @param  array    $relations  关联名
298
     * @param  string   $aggregate  聚合查询方法
299
     * @param  string   $field      字段
300
     * @return void
301
     */
302
    public function relationCount(Model $result, array $relations, string $aggregate = 'sum', string $field = '*'): void
303
    {
304
        foreach ($relations as $key => $relation) {
305
            $closure = $name = null;
306
307
            if ($relation instanceof \Closure) {
308
                $closure  = $relation;
309
                $relation = $key;
310
            } elseif (is_string($key)) {
311
                $name     = $relation;
312
                $relation = $key;
313
            }
314
315
            $relation = App::parseName($relation, 1, false);
316
            $count    = $this->$relation()->relationCount($result, $closure, $aggregate, $field, $name);
317
318
            if (empty($name)) {
319
                $name = App::parseName($relation) . '_' . $aggregate;
320
            }
321
322
            $result->setAttr($name, $count);
323
        }
324
    }
325
326
    /**
327
     * HAS ONE 关联定义
328
     * @access public
329
     * @param  string $model      模型名
330
     * @param  string $foreignKey 关联外键
331
     * @param  string $localKey   当前主键
332
     * @return HasOne
333
     */
334
    public function hasOne(string $model, string $foreignKey = '', string $localKey = ''): HasOne
335
    {
336
        // 记录当前关联信息
337
        $model      = $this->parseModel($model);
338
        $localKey   = $localKey ?: $this->getPk();
0 ignored issues
show
Bug introduced by
It seems like getPk() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

338
        $localKey   = $localKey ?: $this->/** @scrutinizer ignore-call */ getPk();
Loading history...
339
        $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
340
341
        return new HasOne($this, $model, $foreignKey, $localKey);
342
    }
343
344
    /**
345
     * BELONGS TO 关联定义
346
     * @access public
347
     * @param  string $model      模型名
348
     * @param  string $foreignKey 关联外键
349
     * @param  string $localKey   关联主键
350
     * @return BelongsTo
351
     */
352
    public function belongsTo(string $model, string $foreignKey = '', string $localKey = ''): BelongsTo
353
    {
354
        // 记录当前关联信息
355
        $model      = $this->parseModel($model);
356
        $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName());
357
        $localKey   = $localKey ?: (new $model)->getPk();
358
        $trace      = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
359
        $relation   = App::parseName($trace[1]['function']);
360
361
        return new BelongsTo($this, $model, $foreignKey, $localKey, $relation);
362
    }
363
364
    /**
365
     * HAS MANY 关联定义
366
     * @access public
367
     * @param  string $model      模型名
368
     * @param  string $foreignKey 关联外键
369
     * @param  string $localKey   当前主键
370
     * @return HasMany
371
     */
372
    public function hasMany(string $model, string $foreignKey = '', string $localKey = ''): HasMany
373
    {
374
        // 记录当前关联信息
375
        $model      = $this->parseModel($model);
376
        $localKey   = $localKey ?: $this->getPk();
377
        $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
378
379
        return new HasMany($this, $model, $foreignKey, $localKey);
380
    }
381
382
    /**
383
     * HAS MANY 远程关联定义
384
     * @access public
385
     * @param  string $model      模型名
386
     * @param  string $through    中间模型名
387
     * @param  string $foreignKey 关联外键
388
     * @param  string $throughKey 关联外键
389
     * @param  string $localKey   当前主键
390
     * @return HasManyThrough
391
     */
392
    public function hasManyThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = ''): HasManyThrough
393
    {
394
        // 记录当前关联信息
395
        $model      = $this->parseModel($model);
396
        $through    = $this->parseModel($through);
397
        $localKey   = $localKey ?: $this->getPk();
398
        $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
399
        $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName());
400
401
        return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey);
402
    }
403
404
    /**
405
     * BELONGS TO MANY 关联定义
406
     * @access public
407
     * @param  string $model      模型名
408
     * @param  string $table      中间表名
409
     * @param  string $foreignKey 关联外键
410
     * @param  string $localKey   当前模型关联键
411
     * @return BelongsToMany
412
     */
413
    public function belongsToMany(string $model, string $table = '', string $foreignKey = '', string $localKey = ''): BelongsToMany
414
    {
415
        // 记录当前关联信息
416
        $model      = $this->parseModel($model);
417
        $name       = App::parseName(App::classBaseName($model));
418
        $table      = $table ?: App::parseName($this->name) . '_' . $name;
419
        $foreignKey = $foreignKey ?: $name . '_id';
420
        $localKey   = $localKey ?: $this->getForeignKey($this->name);
421
422
        return new BelongsToMany($this, $model, $table, $foreignKey, $localKey);
423
    }
424
425
    /**
426
     * MORPH  One 关联定义
427
     * @access public
428
     * @param  string       $model 模型名
429
     * @param  string|array $morph 多态字段信息
430
     * @param  string       $type  多态类型
431
     * @return MorphOne
432
     */
433
    public function morphOne(string $model, $morph = null, string $type = ''): MorphOne
434
    {
435
        // 记录当前关联信息
436
        $model = $this->parseModel($model);
437
438
        if (is_null($morph)) {
439
            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
440
            $morph = App::parseName($trace[1]['function']);
441
        }
442
443
        if (is_array($morph)) {
444
            list($morphType, $foreignKey) = $morph;
445
        } else {
446
            $morphType  = $morph . '_type';
447
            $foreignKey = $morph . '_id';
448
        }
449
450
        $type = $type ?: get_class($this);
451
452
        return new MorphOne($this, $model, $foreignKey, $morphType, $type);
453
    }
454
455
    /**
456
     * MORPH  MANY 关联定义
457
     * @access public
458
     * @param  string       $model 模型名
459
     * @param  string|array $morph 多态字段信息
460
     * @param  string       $type  多态类型
461
     * @return MorphMany
462
     */
463
    public function morphMany(string $model, $morph = null, string $type = ''): MorphMany
464
    {
465
        // 记录当前关联信息
466
        $model = $this->parseModel($model);
467
468
        if (is_null($morph)) {
469
            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
470
            $morph = App::parseName($trace[1]['function']);
471
        }
472
473
        $type = $type ?: get_class($this);
474
475
        if (is_array($morph)) {
476
            list($morphType, $foreignKey) = $morph;
477
        } else {
478
            $morphType  = $morph . '_type';
479
            $foreignKey = $morph . '_id';
480
        }
481
482
        return new MorphMany($this, $model, $foreignKey, $morphType, $type);
483
    }
484
485
    /**
486
     * MORPH TO 关联定义
487
     * @access public
488
     * @param  string|array $morph 多态字段信息
489
     * @param  array        $alias 多态别名定义
490
     * @return MorphTo
491
     */
492
    public function morphTo($morph = null, array $alias = []): MorphTo
493
    {
494
        $trace    = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
495
        $relation = App::parseName($trace[1]['function']);
496
497
        if (is_null($morph)) {
498
            $morph = $relation;
499
        }
500
501
        // 记录当前关联信息
502
        if (is_array($morph)) {
503
            list($morphType, $foreignKey) = $morph;
504
        } else {
505
            $morphType  = $morph . '_type';
506
            $foreignKey = $morph . '_id';
507
        }
508
509
        return new MorphTo($this, $morphType, $foreignKey, $alias, $relation);
510
    }
511
512
    /**
513
     * 解析模型的完整命名空间
514
     * @access protected
515
     * @param  string $model 模型名(或者完整类名)
516
     * @return string
517
     */
518
    protected function parseModel(string $model): string
519
    {
520
        if (false === strpos($model, '\\')) {
521
            $path = explode('\\', static::class);
522
            array_pop($path);
523
            array_push($path, App::parseName($model, 1));
524
            $model = implode('\\', $path);
525
        }
526
527
        return $model;
528
    }
529
530
    /**
531
     * 获取模型的默认外键名
532
     * @access protected
533
     * @param  string $name 模型名
534
     * @return string
535
     */
536
    protected function getForeignKey(string $name): string
537
    {
538
        if (strpos($name, '\\')) {
539
            $name = App::classBaseName($name);
540
        }
541
542
        return App::parseName($name) . '_id';
543
    }
544
545
    /**
546
     * 检查属性是否为关联属性 如果是则返回关联方法名
547
     * @access protected
548
     * @param  string $attr 关联属性名
549
     * @return string|false
550
     */
551
    protected function isRelationAttr(string $attr)
552
    {
553
        $relation = App::parseName($attr, 1, false);
554
555
        if (method_exists($this, $relation) && !method_exists('think\Model', $relation)) {
556
            return $relation;
557
        }
558
559
        return false;
560
    }
561
562
    /**
563
     * 智能获取关联模型数据
564
     * @access protected
565
     * @param  Relation  $modelRelation 模型关联对象
566
     * @return mixed
567
     */
568
    protected function getRelationData(Relation $modelRelation)
569
    {
570
        if ($this->parent && !$modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) {
571
            return $this->parent;
572
        }
573
574
        // 获取关联数据
575
        return $modelRelation->getRelation();
0 ignored issues
show
Bug introduced by
The method getRelation() does not exist on think\model\Relation. 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

575
        return $modelRelation->/** @scrutinizer ignore-call */ getRelation();
Loading history...
576
    }
577
578
    /**
579
     * 关联数据自动写入检查
580
     * @access protected
581
     * @return void
582
     */
583
    protected function checkAutoRelationWrite(): void
584
    {
585
        foreach ($this->together as $key => $name) {
586
            if (is_array($name)) {
587
                if (key($name) === 0) {
588
                    $this->relationWrite[$key] = [];
589
                    // 绑定关联属性
590
                    foreach ($name as $val) {
591
                        if (isset($this->data[$val])) {
592
                            $this->relationWrite[$key][$val] = $this->data[$val];
593
                        }
594
                    }
595
                } else {
596
                    // 直接传入关联数据
597
                    $this->relationWrite[$key] = $name;
598
                }
599
            } elseif (isset($this->relation[$name])) {
600
                $this->relationWrite[$name] = $this->relation[$name];
601
            } elseif (isset($this->data[$name])) {
602
                $this->relationWrite[$name] = $this->data[$name];
603
                unset($this->data[$name]);
604
            }
605
        }
606
    }
607
608
    /**
609
     * 自动关联数据更新(针对一对一关联)
610
     * @access protected
611
     * @return void
612
     */
613
    protected function autoRelationUpdate(): void
614
    {
615
        foreach ($this->relationWrite as $name => $val) {
616
            if ($val instanceof Model) {
617
                $val->isUpdate()->save();
618
            } else {
619
                $model = $this->getRelation($name);
620
621
                if ($model instanceof Model) {
622
                    $model->isUpdate()->save($val);
623
                }
624
            }
625
        }
626
    }
627
628
    /**
629
     * 自动关联数据写入(针对一对一关联)
630
     * @access protected
631
     * @return void
632
     */
633
    protected function autoRelationInsert(): void
634
    {
635
        foreach ($this->relationWrite as $name => $val) {
636
            $method = App::parseName($name, 1, false);
637
            $this->$method()->save($val);
638
        }
639
    }
640
641
    /**
642
     * 自动关联数据删除(支持一对一及一对多关联)
643
     * @access protected
644
     * @return void
645
     */
646
    protected function autoRelationDelete(): void
647
    {
648
        foreach ($this->relationWrite as $key => $name) {
649
            $name   = is_numeric($key) ? $name : $key;
650
            $result = $this->getRelation($name);
651
652
            if ($result instanceof Model) {
653
                $result->delete();
654
            } elseif ($result instanceof Collection) {
655
                foreach ($result as $model) {
656
                    $model->delete();
657
                }
658
            }
659
        }
660
    }
661
}
662