Completed
Push — 6.0 ( be0b6e...c47dd5 )
by liu
06:54 queued 10s
created

ModelRelationQuery::getModel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 6
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
declare (strict_types = 1);
12
13
namespace think\db\concern;
14
15
use Closure;
16
use think\App;
17
use think\Model;
18
use think\model\Collection as ModelCollection;
19
20
/**
21
 * 模型及关联查询
22
 */
23
trait ModelRelationQuery
24
{
25
26
    /**
27
     * 当前模型对象
28
     * @var Model
29
     */
30
    protected $model;
31
32
    /**
33
     * 指定模型
34
     * @access public
35
     * @param Model $model 模型对象实例
36
     * @return $this
37
     */
38
    public function model(Model $model)
39
    {
40
        $this->model = $model;
41
        return $this;
42
    }
43
44
    /**
45
     * 获取当前的模型对象
46
     * @access public
47
     * @param bool $clear 是否需要清空查询条件
48
     * @return Model|null
49
     */
50
    public function getModel(bool $clear = true)
51
    {
52
        return $this->model ? $this->model->setQuery($this, $clear) : null;
53
    }
54
55
    /**
56
     * 设置需要隐藏的输出属性
57
     * @access public
58
     * @param array $hidden 需要隐藏的字段名
59
     * @return $this
60
     */
61
    public function hidden(array $hidden)
62
    {
63
        $this->options['hidden'] = $hidden;
0 ignored issues
show
Bug Best Practice introduced by
The property options does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
64
        return $this;
65
    }
66
67
    /**
68
     * 设置需要输出的属性
69
     * @access public
70
     * @param array $visible 需要输出的属性
71
     * @return $this
72
     */
73
    public function visible(array $visible)
74
    {
75
        $this->options['visible'] = $visible;
0 ignored issues
show
Bug Best Practice introduced by
The property options does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
76
        return $this;
77
    }
78
79
    /**
80
     * 设置需要追加输出的属性
81
     * @access public
82
     * @param array $append 需要追加的属性
83
     * @return $this
84
     */
85
    public function append(array $append)
86
    {
87
        $this->options['append'] = $append;
0 ignored issues
show
Bug Best Practice introduced by
The property options does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
88
        return $this;
89
    }
90
91
    /**
0 ignored issues
show
Coding Style introduced by
Parameter ...$args should have a doc-comment as per coding-style.
Loading history...
92
     * 添加查询范围
93
     * @access public
94
     * @param array|string|Closure $scope 查询范围定义
95
     * @param array                $args  参数
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter $args does not match actual variable name ...$args
Loading history...
96
     * @return $this
97
     */
98
    public function scope($scope, ...$args)
99
    {
100
        // 查询范围的第一个参数始终是当前查询对象
101
        array_unshift($args, $this);
102
103
        if ($scope instanceof Closure) {
104
            call_user_func_array($scope, $args);
105
            return $this;
106
        }
107
108
        if (is_string($scope)) {
109
            $scope = explode(',', $scope);
110
        }
111
112
        if ($this->model) {
113
            // 检查模型类的查询范围方法
114
            foreach ($scope as $name) {
115
                $method = 'scope' . trim($name);
116
117
                if (method_exists($this->model, $method)) {
118
                    call_user_func_array([$this->model, $method], $args);
119
                }
120
            }
121
        }
122
123
        return $this;
124
    }
125
126
    /**
127
     * 设置关联查询
128
     * @access public
129
     * @param array $relation 关联名称
130
     * @return $this
131
     */
132
    public function relation(array $relation)
133
    {
134
        if (!empty($relation)) {
135
            $this->options['relation'] = $relation;
0 ignored issues
show
Bug Best Practice introduced by
The property options does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
136
        }
137
138
        return $this;
139
    }
140
141
    /**
142
     * 使用搜索器条件搜索字段
143
     * @access public
144
     * @param array  $fields 搜索字段
145
     * @param array  $data   搜索数据
146
     * @param string $prefix 字段前缀标识
147
     * @return $this
148
     */
149
    public function withSearch(array $fields, array $data = [], string $prefix = '')
150
    {
151
        foreach ($fields as $key => $field) {
152
            if ($field instanceof Closure) {
153
                $field($this, $data[$key] ?? null, $data, $prefix);
154
            } elseif ($this->model) {
155
                // 检测搜索器
156
                $fieldName = is_numeric($key) ? $field : $key;
157
                $method    = 'search' . App::parseName($fieldName, 1) . 'Attr';
158
159
                if (method_exists($this->model, $method)) {
160
                    $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
161
                }
162
            }
163
        }
164
165
        return $this;
166
    }
167
168
    /**
169
     * 设置数据字段获取器
170
     * @access public
171
     * @param string|array $name     字段名
172
     * @param callable     $callback 闭包获取器
173
     * @return $this
174
     */
175
    public function withAttr($name, callable $callback = null)
176
    {
177
        if (is_array($name)) {
178
            $this->options['with_attr'] = $name;
0 ignored issues
show
Bug Best Practice introduced by
The property options does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
179
        } else {
180
            $this->options['with_attr'][$name] = $callback;
181
        }
182
183
        return $this;
184
    }
185
186
    /**
187
     * 设置关联查询JOIN预查询
188
     * @access public
189
     * @param array|string $with 关联方法名称
190
     * @return $this
191
     */
192
    public function with($with)
193
    {
194
        if (!empty($with)) {
195
            $this->options['with'] = (array) $with;
0 ignored issues
show
Bug Best Practice introduced by
The property options does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
196
        }
197
198
        return $this;
199
    }
200
201
    /**
202
     * 关联统计
203
     * @access protected
204
     * @param array|string $relations 关联方法名
205
     * @param string       $aggregate 聚合查询方法
206
     * @param string       $field     字段
207
     * @param bool         $subQuery  是否使用子查询
208
     * @return $this
209
     */
210
    protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
211
    {
212
        if (!$subQuery) {
213
            $this->options['with_count'][] = [$relations, $aggregate, $field];
0 ignored issues
show
Bug Best Practice introduced by
The property options does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
214
        } else {
215
            if (!isset($this->options['field'])) {
216
                $this->field('*');
0 ignored issues
show
Bug introduced by
It seems like field() 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

216
                $this->/** @scrutinizer ignore-call */ 
217
                       field('*');
Loading history...
217
            }
218
219
            foreach ((array) $relations as $key => $relation) {
220
                $closure = $aggregateField = null;
221
222
                if ($relation instanceof Closure) {
223
                    $closure  = $relation;
224
                    $relation = $key;
225
                } elseif (!is_int($key)) {
226
                    $aggregateField = $relation;
227
                    $relation       = $key;
228
                }
229
230
                $relation = App::parseName($relation, 1, false);
231
232
                $count = $this->model
233
                    ->$relation()
234
                    ->getRelationCountQuery($closure, $aggregate, $field, $aggregateField);
235
236
                if (empty($aggregateField)) {
237
                    $aggregateField = App::parseName($relation) . '_' . $aggregate;
238
                }
239
240
                $this->field(['(' . $count . ')' => $aggregateField]);
241
            }
242
        }
243
244
        return $this;
245
    }
246
247
    /**
248
     * 关联统计
249
     * @access public
250
     * @param string|array $relation 关联方法名
251
     * @param bool         $subQuery 是否使用子查询
252
     * @return $this
253
     */
254
    public function withCount($relation, bool $subQuery = true)
255
    {
256
        return $this->withAggregate($relation, 'count', '*', $subQuery);
257
    }
258
259
    /**
260
     * 关联统计Sum
261
     * @access public
262
     * @param string|array $relation 关联方法名
263
     * @param string       $field    字段
264
     * @param bool         $subQuery 是否使用子查询
265
     * @return $this
266
     */
267
    public function withSum($relation, string $field, bool $subQuery = true)
268
    {
269
        return $this->withAggregate($relation, 'sum', $field, $subQuery);
270
    }
271
272
    /**
273
     * 关联统计Max
274
     * @access public
275
     * @param string|array $relation 关联方法名
276
     * @param string       $field    字段
277
     * @param bool         $subQuery 是否使用子查询
278
     * @return $this
279
     */
280
    public function withMax($relation, string $field, bool $subQuery = true)
281
    {
282
        return $this->withAggregate($relation, 'max', $field, $subQuery);
283
    }
284
285
    /**
286
     * 关联统计Min
287
     * @access public
288
     * @param string|array $relation 关联方法名
289
     * @param string       $field    字段
290
     * @param bool         $subQuery 是否使用子查询
291
     * @return $this
292
     */
293
    public function withMin($relation, string $field, bool $subQuery = true)
294
    {
295
        return $this->withAggregate($relation, 'min', $field, $subQuery);
296
    }
297
298
    /**
299
     * 关联统计Avg
300
     * @access public
301
     * @param string|array $relation 关联方法名
302
     * @param string       $field    字段
303
     * @param bool         $subQuery 是否使用子查询
304
     * @return $this
305
     */
306
    public function withAvg($relation, string $field, bool $subQuery = true)
307
    {
308
        return $this->withAggregate($relation, 'avg', $field, $subQuery);
309
    }
310
311
    /**
312
     * 查询数据转换为模型数据集对象
313
     * @access protected
314
     * @param array $resultSet 数据集
315
     * @return ModelCollection
316
     */
317
    protected function resultSetToModelCollection(array $resultSet): ModelCollection
318
    {
319
        if (!empty($this->options['collection']) && is_string($this->options['collection'])) {
320
            $collection = $this->options['collection'];
321
        }
322
323
        if (empty($resultSet)) {
324
            return $this->model->toCollection([], $collection ?? null);
325
        }
326
327
        // 检查动态获取器
328
        if (!empty($this->options['with_attr'])) {
329
            foreach ($this->options['with_attr'] as $name => $val) {
330
                if (strpos($name, '.')) {
331
                    list($relation, $field) = explode('.', $name);
332
333
                    $withRelationAttr[$relation][$field] = $val;
334
                    unset($this->options['with_attr'][$name]);
335
                }
336
            }
337
        }
338
339
        $withRelationAttr = $withRelationAttr ?? [];
340
341
        foreach ($resultSet as $key => &$result) {
342
            // 数据转换为模型对象
343
            $this->resultToModel($result, $this->options, true, $withRelationAttr);
344
        }
345
346
        if (!empty($this->options['with'])) {
347
            // 预载入
348
            $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result seems to be defined by a foreach iteration on line 341. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
349
        }
350
351
        if (!empty($this->options['with_join'])) {
352
            // 预载入
353
            $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true);
354
        }
355
356
        // 模型数据集转换
357
        return $this->model->toCollection($resultSet, $collection ?? null);
358
    }
359
360
    /**
361
     * 查询数据转换为模型对象
362
     * @access protected
363
     * @param array $result           查询数据
364
     * @param array $options          查询参数
365
     * @param bool  $resultSet        是否为数据集查询
366
     * @param array $withRelationAttr 关联字段获取器
367
     * @return void
368
     */
369
    protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
370
    {
371
        // 动态获取器
372
        if (!empty($options['with_attr']) && empty($withRelationAttr)) {
373
            foreach ($options['with_attr'] as $name => $val) {
374
                if (strpos($name, '.')) {
375
                    list($relation, $field) = explode('.', $name);
376
377
                    $withRelationAttr[$relation][$field] = $val;
378
                    unset($options['with_attr'][$name]);
379
                }
380
            }
381
        }
382
383
        // JSON 数据处理
384
        if (!empty($options['json'])) {
385
            $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
0 ignored issues
show
Bug introduced by
It seems like jsonResult() 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

385
            $this->/** @scrutinizer ignore-call */ 
386
                   jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
Loading history...
386
        }
387
388
        $result = $this->model
389
            ->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options))
0 ignored issues
show
Bug introduced by
The method getModelUpdateCondition() does not exist on think\db\concern\ModelRelationQuery. Did you maybe mean getModel()? ( Ignorable by Annotation )

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

389
            ->newInstance($result, $resultSet ? null : $this->/** @scrutinizer ignore-call */ getModelUpdateCondition($options))

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
390
            ->setQuery($this);
391
392
        // 动态获取器
393
        if (!empty($options['with_attr'])) {
394
            $result->withAttribute($options['with_attr']);
395
        }
396
397
        // 输出属性控制
398
        if (!empty($options['visible'])) {
399
            $result->visible($options['visible']);
400
        } elseif (!empty($options['hidden'])) {
401
            $result->hidden($options['hidden']);
402
        }
403
404
        if (!empty($options['append'])) {
405
            $result->append($options['append']);
406
        }
407
408
        // 关联查询
409
        if (!empty($options['relation'])) {
410
            $result->relationQuery($options['relation'], $withRelationAttr);
411
        }
412
413
        // 预载入查询
414
        if (!$resultSet && !empty($options['with'])) {
415
            $result->eagerlyResult($result, $options['with'], $withRelationAttr);
416
        }
417
418
        // JOIN预载入查询
419
        if (!$resultSet && !empty($options['with_join'])) {
420
            $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true);
421
        }
422
423
        // 关联统计
424
        if (!empty($options['with_count'])) {
425
            foreach ($options['with_count'] as $val) {
426
                $result->relationCount($result, (array) $val[0], $val[1], $val[2]);
427
            }
428
        }
429
    }
430
431
}
432