Completed
Push — 6.0 ( d95081...82387a )
by liu
03:06
created

HasManyThrough::eagerlyResultSet()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 17
nc 12
nop 4
dl 0
loc 32
ccs 0
cts 18
cp 0
crap 42
rs 9.0777
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 HasManyThrough extends Relation
26
{
27
    /**
28
     * 中间关联表外键
29
     * @var string
30
     */
31
    protected $throughKey;
32
33
    /**
34
     * 中间主键
35
     * @var string
36
     */
37
    protected $throughPk;
38
39
    /**
40
     * 中间表查询对象
41
     * @var Query
42
     */
43
    protected $through;
44
45
    /**
46
     * 架构函数
47
     * @access public
48
     * @param  Model  $parent     上级模型对象
49
     * @param  string $model      模型名
50
     * @param  string $through    中间模型名
51
     * @param  string $foreignKey 关联外键
52
     * @param  string $throughKey 关联外键
53
     * @param  string $localKey   当前主键
54
     * @param  string $throughPk  中间表主键
55
     */
56
    public function __construct(Model $parent, string $model, string $through, string $foreignKey, string $throughKey, string $localKey, string $throughPk)
57
    {
58
        $this->parent     = $parent;
59
        $this->model      = $model;
60
        $this->through    = (new $through)->db();
61
        $this->foreignKey = $foreignKey;
62
        $this->throughKey = $throughKey;
63
        $this->localKey   = $localKey;
64
        $this->throughPk  = $throughPk;
65
        $this->query      = (new $model)->db();
66
    }
67
68
    /**
69
     * 延迟获取关联数据
70
     * @access public
71
     * @param  array   $subRelation 子关联名
72
     * @param  Closure $closure     闭包查询条件
73
     * @return Collection
74
     */
75
    public function getRelation(array $subRelation = [], \Closure $closure = null): Collection
76
    {
77
        if ($closure) {
78
            $closure($this->query);
79
        }
80
81
        $this->baseQuery();
82
83
        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...
84
            ->select()
85
            ->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

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

248
        /** @scrutinizer ignore-call */ 
249
        $modelTable   = $this->parent->getTable();
Loading history...
249
250
        if (false === strpos($field, '.')) {
251
            $field = $alias . '.' . $field;
252
        }
253
254
        return $this->query
255
            ->alias($alias)
256
            ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
257
            ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
258
            ->where($throughTable . '.' . $this->foreignKey, $result->$localKey)
259
            ->$aggregate($field);
260
    }
261
262
    /**
263
     * 创建关联统计子查询
264
     * @access public
265
     * @param  Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
266
     * @param  string  $aggregate 聚合查询方法
267
     * @param  string  $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
268
     * @param  string  $name 统计字段别名
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
269
     * @return string
270
     */
271
    public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
272
    {
273
        if ($closure) {
274
            $closure($this->query, $name);
275
        }
276
277
        $alias        = App::parseName(App::classBaseName($this->model));
278
        $throughTable = $this->through->getTable();
279
        $pk           = $this->throughPk;
280
        $throughKey   = $this->throughKey;
281
        $modelTable   = $this->parent->getTable();
282
283
        if (false === strpos($field, '.')) {
284
            $field = $alias . '.' . $field;
285
        }
286
287
        return $this->query
288
            ->alias($alias)
289
            ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
290
            ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
291
            ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
292
            ->fetchSql()
293
            ->$aggregate($field);
294
    }
295
296
    /**
297
     * 执行基础查询(仅执行一次)
298
     * @access protected
299
     * @return void
300
     */
301
    protected function baseQuery(): void
302
    {
303
        if (empty($this->baseQuery) && $this->parent->getData()) {
304
            $alias        = App::parseName(App::classBaseName($this->model));
305
            $throughTable = $this->through->getTable();
306
            $pk           = $this->throughPk;
307
            $throughKey   = $this->throughKey;
308
            $modelTable   = $this->parent->getTable();
309
            $fields       = $this->getQueryFields($alias);
310
311
            $this->query
312
                ->field($fields)
313
                ->alias($alias)
314
                ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
315
                ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
316
                ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey});
317
318
            $this->baseQuery = true;
319
        }
320
    }
321
322
}
323