Completed
Push — 6.0 ( c6b831...a95596 )
by liu
10:21
created

BelongsToMany::baseQuery()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 2
nop 0
dl 0
loc 10
ccs 0
cts 8
cp 0
crap 12
rs 10
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
12
namespace think\model\relation;
13
14
use Closure;
15
use think\App;
16
use think\Collection;
17
use think\db\BaseQuery as Query;
18
use think\db\Raw;
19
use think\Exception;
20
use think\Model;
21
use think\model\Pivot;
22
use think\model\Relation;
23
use think\Paginator;
24
25
/**
26
 * 多对多关联类
27
 */
28
class BelongsToMany extends Relation
29
{
30
    /**
31
     * 中间表表名
32
     * @var string
33
     */
34
    protected $middle;
35
36
    /**
37
     * 中间表模型名称
38
     * @var string
39
     */
40
    protected $pivotName;
41
42
    /**
43
     * 中间表模型对象
44
     * @var Pivot
45
     */
46
    protected $pivot;
47
48
    /**
49
     * 中间表数据名称
50
     * @var string
51
     */
52
    protected $pivotDataName = 'pivot';
53
54
    /**
55
     * 架构函数
56
     * @access public
57
     * @param  Model  $parent     上级模型对象
58
     * @param  string $model      模型名
59
     * @param  string $middle     中间表/模型名
60
     * @param  string $foreignKey 关联模型外键
61
     * @param  string $localKey   当前模型关联键
62
     */
63
    public function __construct(Model $parent, string $model, string $middle, string $foreignKey, string $localKey)
64
    {
65
        $this->parent     = $parent;
66
        $this->model      = $model;
67
        $this->foreignKey = $foreignKey;
68
        $this->localKey   = $localKey;
69
70
        if (false !== strpos($middle, '\\')) {
71
            $this->pivotName = $middle;
72
            $this->middle    = App::classBaseName($middle);
73
        } else {
74
            $this->middle = $middle;
75
        }
76
77
        $this->query = (new $model)->db();
78
        $this->pivot = $this->newPivot();
79
    }
80
81
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $pivot should have a doc-comment as per coding-style.
Loading history...
82
     * 设置中间表模型
83
     * @access public
84
     * @param  $pivot
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
85
     * @return $this
86
     */
87
    public function pivot(string $pivot)
88
    {
89
        $this->pivotName = $pivot;
90
        return $this;
91
    }
92
93
    /**
94
     * 设置中间表数据名称
95
     * @access public
96
     * @param  string $name
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
97
     * @return $this
98
     */
99
    public function name(string $name)
100
    {
101
        $this->pivotDataName = $name;
102
        return $this;
103
    }
104
105
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $data should have a doc-comment as per coding-style.
Loading history...
106
     * 实例化中间表模型
107
     * @access public
108
     * @param  $data
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
109
     * @return Pivot
110
     * @throws Exception
111
     */
112
    protected function newPivot(array $data = []): Pivot
113
    {
114
        $class = $this->pivotName ?: Pivot::class;
115
        $pivot = new $class($data, $this->parent, $this->middle);
116
117
        if ($pivot instanceof Pivot) {
118
            return $pivot;
119
        } else {
120
            throw new Exception('pivot model must extends: \think\model\Pivot');
121
        }
122
    }
123
124
    /**
125
     * 合成中间表模型
126
     * @access protected
127
     * @param  array|Collection|Paginator $models
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
128
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
129
    protected function hydratePivot(iterable $models)
130
    {
131
        foreach ($models as $model) {
132
            $pivot = [];
133
134
            foreach ($model->getData() as $key => $val) {
135
                if (strpos($key, '__')) {
136
                    list($name, $attr) = explode('__', $key, 2);
137
138
                    if ('pivot' == $name) {
139
                        $pivot[$attr] = $val;
140
                        unset($model->$key);
141
                    }
142
                }
143
            }
144
145
            $model->setRelation($this->pivotDataName, $this->newPivot($pivot));
146
        }
147
    }
148
149
    /**
150
     * 创建关联查询Query对象
151
     * @access protected
152
     * @return Query
153
     */
154
    protected function buildQuery(): Query
155
    {
156
        $foreignKey = $this->foreignKey;
157
        $localKey   = $this->localKey;
158
159
        // 关联查询
160
        $pk = $this->parent->getPk();
161
162
        $condition = ['pivot.' . $localKey, '=', $this->parent->$pk];
163
164
        return $this->belongsToManyQuery($foreignKey, $localKey, [$condition]);
165
    }
166
167
    /**
168
     * 延迟获取关联数据
169
     * @access public
170
     * @param  array    $subRelation 子关联名
171
     * @param  Closure  $closure     闭包查询条件
172
     * @return Collection
173
     */
174
    public function getRelation(array $subRelation = [], Closure $closure = null): Collection
175
    {
176
        if ($closure) {
177
            $closure($this);
178
        }
179
180
        $result = $this->buildQuery()
181
            ->relation($subRelation)
182
            ->select()
183
            ->setParent(clone $this->parent);
0 ignored issues
show
introduced by
The method setParent() does not exist on think\Collection. Maybe you want to declare this class abstract? ( Ignorable by Annotation )

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

183
            ->/** @scrutinizer ignore-call */ setParent(clone $this->parent);
Loading history...
184
185
        $this->hydratePivot($result);
186
187
        return $result;
188
    }
189
190
    /**
191
     * 重载select方法
192
     * @access public
193
     * @param  mixed $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
194
     * @return Collection
195
     */
196
    public function select($data = null): Collection
197
    {
198
        $result = $this->buildQuery()->select($data);
199
        $this->hydratePivot($result);
200
201
        return $result;
202
    }
203
204
    /**
205
     * 重载paginate方法
206
     * @access public
207
     * @param  int|array $listRows
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
208
     * @param  int|bool  $simple
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
209
     * @param  array     $config
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
210
     * @return Paginator
211
     */
212
    public function paginate($listRows = null, $simple = false, $config = []): Paginator
213
    {
214
        $result = $this->buildQuery()->paginate($listRows, $simple, $config);
215
        $this->hydratePivot($result);
216
217
        return $result;
218
    }
219
220
    /**
221
     * 重载find方法
222
     * @access public
223
     * @param  mixed $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
224
     * @return Model
225
     */
226
    public function find($data = null)
227
    {
228
        $result = $this->buildQuery()->find($data);
229
230
        if (!$result->isEmpty()) {
231
            $this->hydratePivot([$result]);
232
        }
233
234
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type array which is incompatible with the documented return type think\Model.
Loading history...
235
    }
236
237
    /**
238
     * 查找多条记录 如果不存在则抛出异常
239
     * @access public
240
     * @param  array|string|Query|\Closure $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
241
     * @return Collection
242
     */
243
    public function selectOrFail($data = null): Collection
244
    {
245
        return $this->buildQuery()->failException(true)->select($data);
246
    }
247
248
    /**
249
     * 查找单条记录 如果不存在则抛出异常
250
     * @access public
251
     * @param  array|string|Query|\Closure $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
252
     * @return Model
253
     */
254
    public function findOrFail($data = null): Model
255
    {
256
        return $this->buildQuery()->failException(true)->find($data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->buildQuery...tion(true)->find($data) returns the type array|null which is incompatible with the type-hinted return think\Model.
Loading history...
257
    }
258
259
    /**
260
     * 根据关联条件查询当前模型
261
     * @access public
262
     * @param  string  $operator 比较操作符
263
     * @param  integer $count    个数
264
     * @param  string  $id       关联表的统计字段
265
     * @param  string  $joinType JOIN类型
266
     * @return Model
267
     */
268
    public function has(string $operator = '>=', $count = 1, $id = '*', string $joinType = 'INNER')
269
    {
270
        return $this->parent;
271
    }
272
273
    /**
274
     * 根据关联条件查询当前模型
275
     * @access public
276
     * @param  mixed  $where 查询条件(数组或者闭包)
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
277
     * @param  mixed  $fields 字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
278
     * @param  string $joinType JOIN类型
279
     * @return Query
280
     * @throws Exception
281
     */
282
    public function hasWhere($where = [], $fields = null, string $joinType = '')
283
    {
284
        throw new Exception('relation not support: hasWhere');
285
    }
286
287
    /**
288
     * 设置中间表的查询条件
289
     * @access public
290
     * @param  string $field
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
291
     * @param  string $op
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
292
     * @param  mixed  $condition
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
293
     * @return $this
294
     */
295
    public function wherePivot($field, $op = null, $condition = null)
296
    {
297
        $this->query->where('pivot.' . $field, $op, $condition);
298
        return $this;
299
    }
300
301
    /**
302
     * 预载入关联查询(数据集)
303
     * @access public
304
     * @param  array   $resultSet   数据集
305
     * @param  string  $relation    当前关联名
306
     * @param  array   $subRelation 子关联名
307
     * @param  Closure $closure     闭包
308
     * @return void
309
     */
310
    public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null): void
311
    {
312
        $localKey = $this->localKey;
313
        $pk       = $resultSet[0]->getPk();
314
        $range    = [];
315
316
        foreach ($resultSet as $result) {
317
            // 获取关联外键列表
318
            if (isset($result->$pk)) {
319
                $range[] = $result->$pk;
320
            }
321
        }
322
323
        if (!empty($range)) {
324
            // 查询关联数据
325
            $data = $this->eagerlyManyToMany([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
326
                ['pivot.' . $localKey, 'in', $range],
327
            ], $relation, $subRelation, $closure);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
328
329
            // 关联属性名
330
            $attr = App::parseName($relation);
331
332
            // 关联数据封装
333
            foreach ($resultSet as $result) {
334
                if (!isset($data[$result->$pk])) {
335
                    $data[$result->$pk] = [];
336
                }
337
338
                $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk], clone $this->parent));
339
            }
340
        }
341
    }
342
343
    /**
344
     * 预载入关联查询(单个数据)
345
     * @access public
346
     * @param  Model   $result      数据对象
347
     * @param  string  $relation    当前关联名
348
     * @param  array   $subRelation 子关联名
349
     * @param  Closure $closure     闭包
350
     * @return void
351
     */
352
    public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null): void
353
    {
354
        $pk = $result->getPk();
355
356
        if (isset($result->$pk)) {
357
            $pk = $result->$pk;
358
            // 查询管理数据
359
            $data = $this->eagerlyManyToMany([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
360
                ['pivot.' . $this->localKey, '=', $pk],
361
            ], $relation, $subRelation, $closure);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
362
363
            // 关联数据封装
364
            if (!isset($data[$pk])) {
365
                $data[$pk] = [];
366
            }
367
368
            $result->setRelation(App::parseName($relation), $this->resultSetBuild($data[$pk], clone $this->parent));
369
        }
370
    }
371
372
    /**
373
     * 关联统计
374
     * @access public
375
     * @param  Model   $result  数据对象
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 2 found
Loading history...
376
     * @param  Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
377
     * @param  string  $aggregate 聚合查询方法
378
     * @param  string  $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
379
     * @param  string  $name 统计字段别名
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
380
     * @return integer
381
     */
382
    public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float
383
    {
384
        $pk = $result->getPk();
385
386
        if (!isset($result->$pk)) {
387
            return 0;
388
        }
389
390
        $pk = $result->$pk;
391
392
        if ($closure) {
393
            $closure($this, $name);
394
        }
395
396
        return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
397
            ['pivot.' . $this->localKey, '=', $pk],
398
        ])->$aggregate($field);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
399
    }
400
401
    /**
402
     * 获取关联统计子查询
403
     * @access public
404
     * @param  Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
405
     * @param  string  $aggregate 聚合查询方法
406
     * @param  string  $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
407
     * @param  string  $name 统计字段别名
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
408
     * @return string
409
     */
410
    public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
411
    {
412
        if ($closure) {
413
            $closure($this, $name);
414
        }
415
416
        return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
417
            [
418
                'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk()),
0 ignored issues
show
Bug introduced by
Are you sure $this->parent->getPk() of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

418
                'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . /** @scrutinizer ignore-type */ $this->parent->getPk()),
Loading history...
Bug introduced by
false of type false is incompatible with the type array expected by parameter $scope of think\Model::db(). ( Ignorable by Annotation )

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

418
                'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(/** @scrutinizer ignore-type */ false)->getTable() . '.' . $this->parent->getPk()),
Loading history...
419
            ],
420
        ])->fetchSql()->$aggregate($field);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
421
    }
422
423
    /**
424
     * 多对多 关联模型预查询
425
     * @access protected
426
     * @param  array   $where       关联预查询条件
427
     * @param  string  $relation    关联名
428
     * @param  array   $subRelation 子关联
429
     * @param  Closure $closure     闭包
430
     * @return array
431
     */
432
    protected function eagerlyManyToMany(array $where, string $relation, array $subRelation = [], Closure $closure = null): array
433
    {
434
        if ($closure) {
435
            $closure($this);
436
        }
437
438
        // 预载入关联查询 支持嵌套预载入
439
        $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)
440
            ->with($subRelation)
441
            ->select();
442
443
        // 组装模型数据
444
        $data = [];
445
        foreach ($list as $set) {
446
            $pivot = [];
447
            foreach ($set->getData() as $key => $val) {
448
                if (strpos($key, '__')) {
449
                    list($name, $attr) = explode('__', $key, 2);
450
                    if ('pivot' == $name) {
451
                        $pivot[$attr] = $val;
452
                        unset($set->$key);
453
                    }
454
                }
455
            }
456
457
            $key = $pivot[$this->localKey];
458
459
            if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
460
                continue;
461
            }
462
463
            $set->setRelation($this->pivotDataName, $this->newPivot($pivot));
464
465
            $data[$key][] = $set;
466
        }
467
468
        return $data;
469
    }
470
471
    /**
472
     * BELONGS TO MANY 关联查询
473
     * @access protected
474
     * @param  string $foreignKey 关联模型关联键
475
     * @param  string $localKey   当前模型关联键
476
     * @param  array  $condition  关联查询条件
477
     * @return Query
478
     */
479
    protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query
480
    {
481
        // 关联查询封装
482
        $tableName = $this->query->getTable();
483
        $table     = $this->pivot->db()->getTable();
484
        $fields    = $this->getQueryFields($tableName);
485
486
        if ($this->withLimit) {
487
            $this->query->limit($this->withLimit);
488
        }
489
490
        $query = $this->query
491
            ->field($fields)
492
            ->tableField(true, $table, 'pivot', 'pivot__');
493
494
        if (empty($this->baseQuery)) {
495
            $relationFk = $this->query->getPk();
496
            $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk)
0 ignored issues
show
Bug introduced by
Are you sure $relationFk of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

496
            $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . /** @scrutinizer ignore-type */ $relationFk)
Loading history...
497
                ->where($condition);
498
        }
499
500
        return $query;
501
    }
502
503
    /**
504
     * 保存(新增)当前关联数据对象
505
     * @access public
506
     * @param  mixed $data  数据 可以使用数组 关联模型对象 和 关联对象的主键
507
     * @param  array $pivot 中间表额外数据
508
     * @return array|Pivot
509
     */
510
    public function save($data, array $pivot = [])
511
    {
512
        // 保存关联表/中间表数据
513
        return $this->attach($data, $pivot);
514
    }
515
516
    /**
517
     * 批量保存当前关联数据对象
518
     * @access public
519
     * @param  iterable $dataSet   数据集
520
     * @param  array    $pivot     中间表额外数据
521
     * @param  bool     $samePivot 额外数据是否相同
522
     * @return array|false
523
     */
524
    public function saveAll(iterable $dataSet, array $pivot = [], bool $samePivot = false)
525
    {
526
        $result = [];
527
528
        foreach ($dataSet as $key => $data) {
529
            if (!$samePivot) {
530
                $pivotData = $pivot[$key] ?? [];
531
            } else {
532
                $pivotData = $pivot;
533
            }
534
535
            $result[] = $this->attach($data, $pivotData);
536
        }
537
538
        return empty($result) ? false : $result;
539
    }
540
541
    /**
542
     * 附加关联的一个中间表数据
543
     * @access public
544
     * @param  mixed $data  数据 可以使用数组、关联模型对象 或者 关联对象的主键
545
     * @param  array $pivot 中间表额外数据
546
     * @return array|Pivot
547
     * @throws Exception
548
     */
549
    public function attach($data, array $pivot = [])
550
    {
551
        if (is_array($data)) {
552
            if (key($data) === 0) {
553
                $id = $data;
554
            } else {
555
                // 保存关联表数据
556
                $model = new $this->model;
557
                $id    = $model->insertGetId($data);
558
            }
559
        } elseif (is_numeric($data) || is_string($data)) {
560
            // 根据关联表主键直接写入中间表
561
            $id = $data;
562
        } elseif ($data instanceof Model) {
563
            // 根据关联表主键直接写入中间表
564
            $relationFk = $data->getPk();
565
            $id         = $data->$relationFk;
566
        }
567
568
        if (!empty($id)) {
569
            // 保存中间表数据
570
            $pk                     = $this->parent->getPk();
571
            $pivot[$this->localKey] = $this->parent->$pk;
572
            $ids                    = (array) $id;
573
574
            foreach ($ids as $id) {
575
                $pivot[$this->foreignKey] = $id;
576
                $this->pivot->replace()
577
                    ->exists(false)
578
                    ->data([])
579
                    ->save($pivot);
580
                $result[] = $this->newPivot($pivot);
581
            }
582
583
            if (count($result) == 1) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result seems to be defined by a foreach iteration on line 574. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
584
                // 返回中间表模型对象
585
                $result = $result[0];
586
            }
587
588
            return $result;
589
        } else {
590
            throw new Exception('miss relation data');
591
        }
592
    }
593
594
    /**
595
     * 判断是否存在关联数据
596
     * @access public
597
     * @param  mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键
598
     * @return Pivot|false
599
     */
600
    public function attached($data)
601
    {
602
        if ($data instanceof Model) {
603
            $id = $data->getKey();
604
        } else {
605
            $id = $data;
606
        }
607
608
        $pivot = $this->pivot
609
            ->where($this->localKey, $this->parent->getKey())
0 ignored issues
show
Bug introduced by
The method where() does not exist on think\model\Pivot. 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

609
            ->/** @scrutinizer ignore-call */ where($this->localKey, $this->parent->getKey())
Loading history...
610
            ->where($this->foreignKey, $id)
611
            ->find();
612
613
        return $pivot ?: false;
614
    }
615
616
    /**
617
     * 解除关联的一个中间表数据
618
     * @access public
619
     * @param  integer|array $data        数据 可以使用关联对象的主键
620
     * @param  bool          $relationDel 是否同时删除关联表数据
621
     * @return integer
622
     */
623
    public function detach($data = null, bool $relationDel = false): int
624
    {
625
        if (is_array($data)) {
626
            $id = $data;
627
        } elseif (is_numeric($data) || is_string($data)) {
628
            // 根据关联表主键直接写入中间表
629
            $id = $data;
630
        } elseif ($data instanceof Model) {
631
            // 根据关联表主键直接写入中间表
632
            $relationFk = $data->getPk();
633
            $id         = $data->$relationFk;
634
        }
635
636
        // 删除中间表数据
637
        $pk      = $this->parent->getPk();
638
        $pivot   = [];
639
        $pivot[] = [$this->localKey, '=', $this->parent->$pk];
640
641
        if (isset($id)) {
642
            $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id];
643
        }
644
645
        $result = $this->pivot->where($pivot)->delete();
646
647
        // 删除关联表数据
648
        if (isset($id) && $relationDel) {
649
            $model = $this->model;
650
            $model::destroy($id);
651
        }
652
653
        return $result;
654
    }
655
656
    /**
657
     * 数据同步
658
     * @access public
659
     * @param  array $ids
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
660
     * @param  bool  $detaching
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
661
     * @return array
662
     */
663
    public function sync(array $ids, bool $detaching = true): array
664
    {
665
        $changes = [
666
            'attached' => [],
667
            'detached' => [],
668
            'updated'  => [],
669
        ];
670
671
        $pk = $this->parent->getPk();
672
673
        $current = $this->pivot
674
            ->where($this->localKey, $this->parent->$pk)
675
            ->column($this->foreignKey);
676
677
        $records = [];
678
679
        foreach ($ids as $key => $value) {
680
            if (!is_array($value)) {
681
                $records[$value] = [];
682
            } else {
683
                $records[$key] = $value;
684
            }
685
        }
686
687
        $detach = array_diff($current, array_keys($records));
688
689
        if ($detaching && count($detach) > 0) {
690
            $this->detach($detach);
691
            $changes['detached'] = $detach;
692
        }
693
694
        foreach ($records as $id => $attributes) {
695
            if (!in_array($id, $current)) {
696
                $this->attach($id, $attributes);
697
                $changes['attached'][] = $id;
698
            } elseif (count($attributes) > 0 && $this->attach($id, $attributes)) {
699
                $changes['updated'][] = $id;
700
            }
701
        }
702
703
        return $changes;
704
    }
705
706
}
707