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

HasMany::hasWhere()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 2
nop 3
dl 0
loc 18
ccs 0
cts 13
cp 0
crap 6
rs 9.8666
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
declare (strict_types = 1);
12
13
namespace think\model\relation;
14
15
use Closure;
16
use think\App;
17
use think\Collection;
18
use think\db\Query;
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 HasMany extends Relation
26
{
27
    /**
28
     * 架构函数
29
     * @access public
30
     * @param  Model  $parent     上级模型对象
31
     * @param  string $model      模型名
32
     * @param  string $foreignKey 关联外键
33
     * @param  string $localKey   当前模型主键
34
     */
35
    public function __construct(Model $parent, string $model, string $foreignKey, string $localKey)
36
    {
37
        $this->parent     = $parent;
38
        $this->model      = $model;
39
        $this->foreignKey = $foreignKey;
40
        $this->localKey   = $localKey;
41
        $this->query      = (new $model)->db();
42
43
        if (get_class($parent) == $model) {
44
            $this->selfRelation = true;
45
        }
46
    }
47
48
    /**
49
     * 延迟获取关联数据
50
     * @access public
51
     * @param  array   $subRelation 子关联名
52
     * @param  Closure $closure     闭包查询条件
53
     * @return Collection
54
     */
55
    public function getRelation(array $subRelation = [], Closure $closure = null): Collection
56
    {
57
        if ($closure) {
58
            $closure($this->query);
59
        }
60
61
        return $this->query
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->query->whe...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...
62
            ->where($this->foreignKey, $this->parent->{$this->localKey})
63
            ->relation($subRelation)
64
            ->select()
65
            ->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

65
            ->/** @scrutinizer ignore-call */ setParent(clone $this->parent);
Loading history...
66
    }
67
68
    /**
69
     * 预载入关联查询
70
     * @access public
71
     * @param  array   $resultSet   数据集
72
     * @param  string  $relation    当前关联名
73
     * @param  array   $subRelation 子关联名
74
     * @param  Closure $closure     闭包
75
     * @return void
76
     */
77
    public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null): void
78
    {
79
        $localKey = $this->localKey;
80
        $range    = [];
81
82
        foreach ($resultSet as $result) {
83
            // 获取关联外键列表
84
            if (isset($result->$localKey)) {
85
                $range[] = $result->$localKey;
86
            }
87
        }
88
89
        if (!empty($range)) {
90
            $where = [
91
                [$this->foreignKey, 'in', $range],
92
            ];
93
            $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure);
94
95
            // 关联属性名
96
            $attr = App::parseName($relation);
97
98
            // 关联数据封装
99
            foreach ($resultSet as $result) {
100
                $pk = $result->$localKey;
101
                if (!isset($data[$pk])) {
102
                    $data[$pk] = [];
103
                }
104
105
                $result->setRelation($attr, $this->resultSetBuild($data[$pk], clone $this->parent));
106
            }
107
        }
108
    }
109
110
    /**
111
     * 预载入关联查询
112
     * @access public
113
     * @param  Model   $result      数据对象
114
     * @param  string  $relation    当前关联名
115
     * @param  array   $subRelation 子关联名
116
     * @param  Closure $closure     闭包
117
     * @return void
118
     */
119
    public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null): void
120
    {
121
        $localKey = $this->localKey;
122
123
        if (isset($result->$localKey)) {
124
            $pk    = $result->$localKey;
125
            $where = [$this->foreignKey, '=', $pk];
126
            $data  = $this->eagerlyOneToMany([$where], $relation, $subRelation, $closure);
127
128
            // 关联数据封装
129
            if (!isset($data[$pk])) {
130
                $data[$pk] = [];
131
            }
132
133
            $result->setRelation(App::parseName($relation), $this->resultSetBuild($data[$pk], clone $this->parent));
134
        }
135
    }
136
137
    /**
138
     * 关联统计
139
     * @access public
140
     * @param  Model   $result  数据对象
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 2 found
Loading history...
141
     * @param  Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
142
     * @param  string  $aggregate 聚合查询方法
143
     * @param  string  $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
144
     * @param  string  $name 统计字段别名
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
145
     * @return integer
146
     */
147
    public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', string &$name = null)
148
    {
149
        $localKey = $this->localKey;
150
151
        if (!isset($result->$localKey)) {
152
            return 0;
153
        }
154
155
        if ($closure) {
0 ignored issues
show
introduced by
$closure is of type Closure, thus it always evaluated to true.
Loading history...
156
            $closure($this->query, $name);
157
        }
158
159
        return $this->query
160
            ->where($this->foreignKey, '=', $result->$localKey)
161
            ->$aggregate($field);
162
    }
163
164
    /**
165
     * 创建关联统计子查询
166
     * @access public
167
     * @param  Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
168
     * @param  string  $aggregate 聚合查询方法
169
     * @param  string  $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
170
     * @param  string  $name 统计字段别名
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
171
     * @return string
172
     */
173
    public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
174
    {
175
        if ($closure) {
176
            $return = $closure($this->query);
177
            if ($return && is_string($return)) {
178
                $name = $return;
179
            }
180
        }
181
182
        return $this->query->alias($aggregate . '_table')
183
            ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
0 ignored issues
show
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

183
            ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->/** @scrutinizer ignore-call */ getTable() . '.' . $this->localKey)
Loading history...
184
            ->fetchSql()
185
            ->$aggregate($field);
186
    }
187
188
    /**
189
     * 一对多 关联模型预查询
190
     * @access public
191
     * @param  array   $where       关联预查询条件
192
     * @param  string  $relation    关联名
193
     * @param  array   $subRelation 子关联
194
     * @param  Closure $closure
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
195
     * @return array
196
     */
197
    protected function eagerlyOneToMany(array $where, string $relation, array $subRelation = [], Closure $closure = null): array
198
    {
199
        $foreignKey = $this->foreignKey;
200
201
        $this->query->removeWhereField($this->foreignKey);
202
203
        // 预载入关联查询 支持嵌套预载入
204
        if ($closure) {
205
            $closure($this->query);
206
        }
207
208
        $list = $this->query->where($where)->with($subRelation)->select();
209
210
        // 组装模型数据
211
        $data = [];
212
213
        foreach ($list as $set) {
214
            $data[$set->$foreignKey][] = $set;
215
        }
216
217
        return $data;
218
    }
219
220
    /**
221
     * 保存(新增)当前关联数据对象
222
     * @access public
223
     * @param  mixed   $data 数据 可以使用数组 关联模型对象
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
224
     * @param  boolean $replace 是否自动识别更新和写入
225
     * @return Model|false
226
     */
227
    public function save($data, bool $replace = true)
228
    {
229
        $model = $this->make();
230
231
        return $model->replace($replace)->save($data) ? $model : false;
232
    }
233
234
    /**
235
     * 创建关联对象实例
236
     * @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...
237
     * @return Model
238
     */
239
    public function make($data = []): Model
240
    {
241
        if ($data instanceof Model) {
242
            $data = $data->getData();
243
        }
244
245
        // 保存关联表数据
246
        $data[$this->foreignKey] = $this->parent->{$this->localKey};
247
248
        return new $this->model($data);
249
    }
250
251
    /**
252
     * 批量保存当前关联数据对象
253
     * @access public
254
     * @param  iterable $dataSet 数据集
255
     * @param  boolean  $replace 是否自动识别更新和写入
256
     * @return array|false
257
     */
258
    public function saveAll(iterable $dataSet, bool $replace = true)
259
    {
260
        $result = [];
261
262
        foreach ($dataSet as $key => $data) {
263
            $result[] = $this->save($data, $replace);
264
        }
265
266
        return empty($result) ? false : $result;
267
    }
268
269
    /**
270
     * 根据关联条件查询当前模型
271
     * @access public
272
     * @param  string  $operator 比较操作符
273
     * @param  integer $count    个数
274
     * @param  string  $id       关联表的统计字段
275
     * @param  string  $joinType JOIN类型
276
     * @return Query
277
     */
278
    public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER'): Query
279
    {
280
        $table = $this->query->getTable();
281
282
        $model    = App::classBaseName($this->parent);
283
        $relation = App::classBaseName($this->model);
284
285
        return $this->parent->db()
286
            ->alias($model)
287
            ->field($model . '.*')
288
            ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType)
289
            ->group($relation . '.' . $this->foreignKey)
290
            ->having('count(' . $id . ')' . $operator . $count);
291
    }
292
293
    /**
294
     * 根据关联条件查询当前模型
295
     * @access public
296
     * @param  mixed  $where 查询条件(数组或者闭包)
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
297
     * @param  mixed  $fields 字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
298
     * @param  string $joinType JOIN类型
299
     * @return Query
300
     */
301
    public function hasWhere($where = [], $fields = null, string $joinType = ''): Query
302
    {
303
        $table    = $this->query->getTable();
304
        $model    = App::classBaseName($this->parent);
305
        $relation = App::classBaseName($this->model);
306
307
        if (is_array($where)) {
308
            $this->getQueryWhere($where, $relation);
309
        }
310
311
        $fields = $this->getRelationQueryFields($fields, $model);
312
313
        return $this->parent->db()
314
            ->alias($model)
315
            ->group($model . '.' . $this->localKey)
316
            ->field($fields)
317
            ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey)
318
            ->where($where);
319
    }
320
321
    /**
322
     * 执行基础查询(仅执行一次)
323
     * @access protected
324
     * @return void
325
     */
326
    protected function baseQuery(): void
327
    {
328
        if (empty($this->baseQuery)) {
329
            if (isset($this->parent->{$this->localKey})) {
330
                // 关联查询带入关联条件
331
                $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
332
            }
333
334
            $this->baseQuery = true;
335
        }
336
    }
337
338
}
339