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

HasOneThrough   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 305
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 112
dl 0
loc 305
ccs 0
cts 119
cp 0
rs 10
c 0
b 0
f 0
wmc 27

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getRelation() 0 15 3
A eagerlyResult() 0 21 2
A hasWhere() 0 3 1
A __construct() 0 10 1
A has() 0 3 1
B eagerlyResultSet() 0 36 6
A eagerlyWhere() 0 20 3
A relationCount() 0 28 4
A getRelationCountQuery() 0 23 3
A baseQuery() 0 19 3
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\db\Query;
17
use think\Exception;
18
use think\Model;
19
use think\model\Relation;
20
21
/**
22
 * 远程一对一关联类
23
 */
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...
24
class HasOneThrough extends Relation
25
{
26
    /**
27
     * 中间关联表外键
28
     * @var string
29
     */
30
    protected $throughKey;
31
32
    /**
33
     * 中间主键
34
     * @var string
35
     */
36
    protected $throughPk;
37
38
    /**
39
     * 中间表查询对象
40
     * @var Query
41
     */
42
    protected $through;
43
44
    /**
45
     * 架构函数
46
     * @access public
47
     * @param  Model  $parent     上级模型对象
48
     * @param  string $model      模型名
49
     * @param  string $through    中间模型名
50
     * @param  string $foreignKey 关联外键
51
     * @param  string $throughKey 关联外键
52
     * @param  string $localKey   当前主键
53
     * @param  string $throughPk  中间表主键
54
     */
55
    public function __construct(Model $parent, string $model, string $through, string $foreignKey, string $throughKey, string $localKey, string $throughPk)
56
    {
57
        $this->parent     = $parent;
58
        $this->model      = $model;
59
        $this->through    = (new $through)->db();
60
        $this->foreignKey = $foreignKey;
61
        $this->throughKey = $throughKey;
62
        $this->localKey   = $localKey;
63
        $this->throughPk  = $throughPk;
64
        $this->query      = (new $model)->db();
65
    }
66
67
    /**
68
     * 延迟获取关联数据
69
     * @access public
70
     * @param  array   $subRelation 子关联名
71
     * @param  Closure $closure     闭包查询条件
72
     * @return Model
73
     */
74
    public function getRelation(array $subRelation = [], \Closure $closure = null)
75
    {
76
        if ($closure) {
77
            $closure($this->query);
78
        }
79
80
        $this->baseQuery();
81
82
        $relationModel = $this->query->relation($subRelation)->find();
83
84
        if ($relationModel) {
0 ignored issues
show
introduced by
$relationModel is of type think\Model, thus it always evaluated to true.
Loading history...
85
            $relationModel->setParent(clone $this->parent);
86
        }
87
88
        return $relationModel;
89
    }
90
91
    /**
92
     * 根据关联条件查询当前模型
93
     * @access public
94
     * @param  string  $operator 比较操作符
95
     * @param  integer $count    个数
96
     * @param  string  $id       关联表的统计字段
97
     * @param  string  $joinType JOIN类型
98
     * @return Query
99
     */
100
    public function has(string $operator = '>=', int $count = 1, string $id = '*', $joinType = '')
101
    {
102
        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...
103
    }
104
105
    /**
106
     * 根据关联条件查询当前模型
107
     * @access public
108
     * @param  mixed  $where 查询条件(数组或者闭包)
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 1 found
Loading history...
109
     * @param  mixed  $fields 字段
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
110
     * @param  string $joinType JOIN类型
111
     * @return Query
112
     */
113
    public function hasWhere($where = [], $fields = null, $joinType = '')
114
    {
115
        throw new Exception('relation not support: hasWhere');
116
    }
117
118
    /**
119
     * 预载入关联查询(数据集)
120
     * @access protected
121
     * @param  array   $resultSet   数据集
122
     * @param  string  $relation    当前关联名
123
     * @param  array   $subRelation 子关联名
124
     * @param  Closure $closure     闭包
125
     * @return void
126
     */
127
    public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null): void
128
    {
129
        $localKey   = $this->localKey;
130
        $foreignKey = $this->foreignKey;
131
132
        $range = [];
133
        foreach ($resultSet as $result) {
134
            // 获取关联外键列表
135
            if (isset($result->$localKey)) {
136
                $range[] = $result->$localKey;
137
            }
138
        }
139
140
        if (!empty($range)) {
141
            $this->query->removeWhereField($foreignKey);
142
143
            $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...
144
                [$this->foreignKey, 'in', $range],
145
            ], $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...
146
147
            // 关联属性名
148
            $attr = App::parseName($relation);
149
150
            // 关联数据封装
151
            foreach ($resultSet as $result) {
152
                // 关联模型
153
                if (!isset($data[$result->$localKey])) {
154
                    $relationModel = null;
155
                } else {
156
                    $relationModel = $data[$result->$localKey];
157
                    $relationModel->setParent(clone $result);
158
                    $relationModel->exists(true);
159
                }
160
161
                // 设置关联属性
162
                $result->setRelation($attr, $relationModel);
163
            }
164
        }
165
    }
166
167
    /**
168
     * 预载入关联查询(数据)
169
     * @access protected
170
     * @param  Model   $result      数据对象
171
     * @param  string  $relation    当前关联名
172
     * @param  array   $subRelation 子关联名
173
     * @param  Closure $closure     闭包
174
     * @return void
175
     */
176
    public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null): void
177
    {
178
        $localKey   = $this->localKey;
179
        $foreignKey = $this->foreignKey;
180
181
        $this->query->removeWhereField($foreignKey);
182
183
        $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...
184
            [$foreignKey, '=', $result->$localKey],
185
        ], $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...
186
187
        // 关联模型
188
        if (!isset($data[$result->$localKey])) {
189
            $relationModel = null;
190
        } else {
191
            $relationModel = $data[$result->$localKey];
192
            $relationModel->setParent(clone $result);
193
            $relationModel->exists(true);
194
        }
195
196
        $result->setRelation(App::parseName($relation), $relationModel);
197
    }
198
199
    /**
200
     * 关联模型预查询
201
     * @access public
202
     * @param  array   $where       关联预查询条件
203
     * @param  string  $key         关联键名
204
     * @param  string  $relation    关联名
205
     * @param  array   $subRelation 子关联
206
     * @param  Closure $closure
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
207
     * @return array
208
     */
209
    protected function eagerlyWhere(array $where, string $key, string $relation, array $subRelation = [], Closure $closure = null)
210
    {
211
        // 预载入关联查询 支持嵌套预载入
212
        $keys = $this->through->where($where)->column($this->throughPk, $this->foreignKey);
213
214
        if ($closure) {
215
            $closure($this->query);
216
        }
217
218
        $list = $this->query->where($this->throughKey, 'in', $keys)->select();
219
220
        // 组装模型数据
221
        $data = [];
222
        $keys = array_flip($keys);
223
224
        foreach ($list as $set) {
225
            $data[$keys[$set->{$this->throughKey}]] = $set;
226
        }
227
228
        return $data;
229
    }
230
231
    /**
232
     * 关联统计
233
     * @access public
234
     * @param  Model   $result  数据对象
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 2 found
Loading history...
235
     * @param  Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
236
     * @param  string  $aggregate 聚合查询方法
237
     * @param  string  $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
238
     * @param  string  $name 统计字段别名
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
239
     * @return integer
240
     */
241
    public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', string &$name = null)
242
    {
243
        $localKey = $this->localKey;
244
245
        if (!isset($result->$localKey)) {
246
            return 0;
247
        }
248
249
        if ($closure) {
0 ignored issues
show
introduced by
$closure is of type Closure, thus it always evaluated to true.
Loading history...
250
            $closure($this->query, $name);
251
        }
252
253
        $alias        = App::parseName(App::classBaseName($this->model));
254
        $throughTable = $this->through->getTable();
255
        $pk           = $this->throughPk;
256
        $throughKey   = $this->throughKey;
257
        $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

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

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

316
            $pk           = (/** @scrutinizer ignore-call */ new $through)->getPk();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
317
            $throughKey   = $this->throughKey;
318
            $modelTable   = $this->parent->getTable();
319
            $fields       = $this->getQueryFields($alias);
320
321
            $this->query
322
                ->field($fields)
323
                ->alias($alias)
324
                ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
325
                ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
326
                ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey});
327
328
            $this->baseQuery = true;
329
        }
330
    }
331
332
}
333