Completed
Push — 6.0 ( 6faeca...d95081 )
by liu
02:54
created

MorphMany::eagerlyResultSet()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 18
nc 12
nop 4
dl 0
loc 32
ccs 0
cts 18
cp 0
crap 42
rs 9.0444
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
1 ignored issue
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
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\Query;
18
use think\Exception;
19
use think\Model;
20
use think\model\Relation;
21
22
/**
23
 * 多态一对多关联
24
 */
5 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
25
class MorphMany extends Relation
26
{
27
28
    /**
29
     * 多态关联外键
30
     * @var string
31
     */
32
    protected $morphKey;
33
    /**
34
     * 多态字段名
35
     * @var string
36
     */
37
    protected $morphType;
38
39
    /**
40
     * 多态类型
41
     * @var string
42
     */
43
    protected $type;
44
45
    /**
46
     * 架构函数
47
     * @access public
48
     * @param  Model  $parent    上级模型对象
49
     * @param  string $model     模型名
50
     * @param  string $morphKey  关联外键
51
     * @param  string $morphType 多态字段名
52
     * @param  string $type      多态类型
53
     */
54
    public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type)
55
    {
56
        $this->parent    = $parent;
57
        $this->model     = $model;
58
        $this->type      = $type;
59
        $this->morphKey  = $morphKey;
60
        $this->morphType = $morphType;
61
        $this->query     = (new $model)->db();
62
    }
63
64
    /**
65
     * 延迟获取关联数据
66
     * @access public
67
     * @param  array   $subRelation 子关联名
68
     * @param  Closure $closure     闭包查询条件
69
     * @return Collection
70
     */
71
    public function getRelation(array $subRelation = [], Closure $closure = null): Collection
72
    {
73
        if ($closure) {
74
            $closure($this->query);
75
        }
76
77
        $this->baseQuery();
78
79
        return $this->query->relation($subRelation)
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->rel...nt(clone $this->parent) could return the type array which is incompatible with the type-hinted return think\Collection. Consider adding an additional type-check to rule them out.
Loading history...
80
            ->select()
81
            ->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

81
            ->/** @scrutinizer ignore-call */ setParent(clone $this->parent);
Loading history...
82
    }
83
84
    /**
85
     * 根据关联条件查询当前模型
86
     * @access public
87
     * @param  string  $operator 比较操作符
88
     * @param  integer $count    个数
89
     * @param  string  $id       关联表的统计字段
90
     * @param  string  $joinType JOIN类型
91
     * @return Query
92
     */
93
    public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '')
94
    {
95
        throw new Exception('relation not support: has');
96
    }
97
98
    /**
99
     * 根据关联条件查询当前模型
100
     * @access public
101
     * @param  mixed  $where 查询条件(数组或者闭包)
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
102
     * @param  mixed  $fields 字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
103
     * @param  string $joinType JOIN类型
104
     * @return Query
105
     */
106
    public function hasWhere($where = [], $fields = null, string $joinType = '')
107
    {
108
        throw new Exception('relation not support: hasWhere');
109
    }
110
111
    /**
112
     * 预载入关联查询
113
     * @access public
114
     * @param  array   $resultSet   数据集
115
     * @param  string  $relation    当前关联名
116
     * @param  array   $subRelation 子关联名
117
     * @param  Closure $closure     闭包
118
     * @return void
119
     */
120
    public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null): void
121
    {
122
        $morphType = $this->morphType;
123
        $morphKey  = $this->morphKey;
124
        $type      = $this->type;
125
        $range     = [];
126
127
        foreach ($resultSet as $result) {
128
            $pk = $result->getPk();
129
            // 获取关联外键列表
130
            if (isset($result->$pk)) {
131
                $range[] = $result->$pk;
132
            }
133
        }
134
135
        if (!empty($range)) {
136
            $where = [
137
                [$morphKey, 'in', $range],
138
                [$morphType, '=', $type],
139
            ];
140
            $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure);
141
142
            // 关联属性名
143
            $attr = App::parseName($relation);
144
145
            // 关联数据封装
146
            foreach ($resultSet as $result) {
147
                if (!isset($data[$result->$pk])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pk seems to be defined by a foreach iteration on line 127. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
148
                    $data[$result->$pk] = [];
149
                }
150
151
                $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk], clone $this->parent));
152
            }
153
        }
154
    }
155
156
    /**
157
     * 预载入关联查询
158
     * @access public
159
     * @param  Model   $result      数据对象
160
     * @param  string  $relation    当前关联名
161
     * @param  array   $subRelation 子关联名
162
     * @param  Closure $closure     闭包
163
     * @return void
164
     */
165
    public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null): void
166
    {
167
        $pk = $result->getPk();
168
169
        if (isset($result->$pk)) {
170
            $key  = $result->$pk;
171
            $data = $this->eagerlyMorphToMany([
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...
172
                [$this->morphKey, '=', $key],
173
                [$this->morphType, '=', $this->type],
174
            ], $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...
175
176
            if (!isset($data[$key])) {
177
                $data[$key] = [];
178
            }
179
180
            $result->setRelation(App::parseName($relation), $this->resultSetBuild($data[$key], clone $this->parent));
181
        }
182
    }
183
184
    /**
185
     * 关联统计
186
     * @access public
187
     * @param  Model   $result  数据对象
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 2 found
Loading history...
188
     * @param  Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
189
     * @param  string  $aggregate 聚合查询方法
190
     * @param  string  $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
191
     * @param  string  $name 统计字段别名
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
192
     * @return integer
193
     */
194
    public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', string &$name = null)
195
    {
196
        $pk = $result->getPk();
197
198
        if (!isset($result->$pk)) {
199
            return 0;
200
        }
201
202
        if ($closure) {
0 ignored issues
show
introduced by
$closure is of type Closure, thus it always evaluated to true.
Loading history...
203
            $closure($this->query, $name);
204
        }
205
206
        return $this->query
207
            ->where([
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...
208
                [$this->morphKey, '=', $result->$pk],
209
                [$this->morphType, '=', $this->type],
210
            ])
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...
211
            ->$aggregate($field);
212
    }
213
214
    /**
215
     * 获取关联统计子查询
216
     * @access public
217
     * @param  Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
218
     * @param  string  $aggregate 聚合查询方法
219
     * @param  string  $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
220
     * @param  string  $name 统计字段别名
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
221
     * @return string
222
     */
223
    public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
224
    {
225
        if ($closure) {
226
            $return = $closure($this->query);
227
228
            if ($return && is_string($return)) {
229
                $name = $return;
230
            }
231
        }
232
233
        return $this->query
234
            ->whereExp($this->morphKey, '=' . $this->parent->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

234
            ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . /** @scrutinizer ignore-type */ $this->parent->getPk())
Loading history...
Bug introduced by
The method getTable() 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

234
            ->whereExp($this->morphKey, '=' . $this->parent->/** @scrutinizer ignore-call */ getTable() . '.' . $this->parent->getPk())
Loading history...
235
            ->where($this->morphType, '=', $this->type)
236
            ->fetchSql()
237
            ->$aggregate($field);
238
    }
239
240
    /**
241
     * 多态一对多 关联模型预查询
242
     * @access protected
243
     * @param  array   $where       关联预查询条件
244
     * @param  string  $relation    关联名
245
     * @param  array   $subRelation 子关联
246
     * @param  Closure $closure     闭包
247
     * @return array
248
     */
249
    protected function eagerlyMorphToMany(array $where, string $relation, array $subRelation = [], Closure $closure = null): array
250
    {
251
        // 预载入关联查询 支持嵌套预载入
252
        $this->query->removeOption('where');
253
254
        if ($closure) {
255
            $closure($this->query);
256
        }
257
258
        $list     = $this->query->where($where)->with($subRelation)->select();
259
        $morphKey = $this->morphKey;
260
261
        // 组装模型数据
262
        $data = [];
263
        foreach ($list as $set) {
264
            $data[$set->$morphKey][] = $set;
265
        }
266
267
        return $data;
268
    }
269
270
    /**
271
     * 保存(新增)当前关联数据对象
272
     * @access public
273
     * @param  mixed $data 数据 可以使用数组 关联模型对象
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
274
     * @param  bool  $replace 是否自动识别更新和写入
275
     * @return Model|false
276
     */
277
    public function save($data, bool $replace = true)
278
    {
279
        $model = $this->make();
280
281
        return $model->replace($replace)->save($data) ? $model : false;
282
    }
283
284
    /**
285
     * 创建关联对象实例
286
     * @param array|Model $data
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
287
     * @return Model
288
     */
289
    public function make($data = []): Model
290
    {
291
        if ($data instanceof Model) {
292
            $data = $data->getData();
293
        }
294
295
        // 保存关联表数据
296
        $pk = $this->parent->getPk();
297
298
        $data[$this->morphKey]  = $this->parent->$pk;
299
        $data[$this->morphType] = $this->type;
300
301
        return new $this->model($data);
302
    }
303
304
    /**
305
     * 批量保存当前关联数据对象
306
     * @access public
307
     * @param  iterable $dataSet 数据集
308
     * @param  boolean  $replace 是否自动识别更新和写入
309
     * @return array|false
310
     */
311
    public function saveAll(iterable $dataSet, bool $replace = true)
312
    {
313
        $result = [];
314
315
        foreach ($dataSet as $key => $data) {
316
            $result[] = $this->save($data, $replace);
317
        }
318
319
        return empty($result) ? false : $result;
320
    }
321
322
    /**
323
     * 执行基础查询(仅执行一次)
324
     * @access protected
325
     * @return void
326
     */
327
    protected function baseQuery(): void
328
    {
329
        if (empty($this->baseQuery) && $this->parent->getData()) {
330
            $pk = $this->parent->getPk();
331
332
            $this->query->where([
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...
333
                [$this->morphKey, '=', $this->parent->$pk],
334
                [$this->morphType, '=', $this->type],
335
            ]);
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...
336
337
            $this->baseQuery = true;
338
        }
339
    }
340
341
}
342