Passed
Push — 5.1 ( 1be377...189c0c )
by liu
07:26
created

HasMany::save()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2018 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 think\db\Query;
15
use think\Loader;
16
use think\Model;
17
use think\model\Relation;
18
19
class HasMany extends Relation
20
{
21
    /**
22
     * 架构函数
23
     * @access public
24
     * @param  Model  $parent     上级模型对象
25
     * @param  string $model      模型名
26
     * @param  string $foreignKey 关联外键
27
     * @param  string $localKey   当前模型主键
28
     */
29
    public function __construct(Model $parent, $model, $foreignKey, $localKey)
30
    {
31
        $this->parent     = $parent;
32
        $this->model      = $model;
0 ignored issues
show
Documentation Bug introduced by
It seems like $model of type string is incompatible with the declared type think\Model of property $model.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
33
        $this->foreignKey = $foreignKey;
34
        $this->localKey   = $localKey;
35
        $this->query      = (new $model)->db();
36
37
        if (get_class($parent) == $model) {
38
            $this->selfRelation = true;
39
        }
40
    }
41
42
    /**
43
     * 延迟获取关联数据
44
     * @access public
45
     * @param  string   $subRelation 子关联名
46
     * @param  \Closure $closure     闭包查询条件
47
     * @return \think\Collection
48
     */
49
    public function getRelation($subRelation = '', $closure = null)
50
    {
51
        if ($closure) {
52
            $closure($this->query);
53
        }
54
55
        $list = $this->query
56
            ->where($this->foreignKey, $this->parent->{$this->localKey})
57
            ->relation($subRelation)
58
            ->select();
59
60
        $parent = clone $this->parent;
61
62
        foreach ($list as &$model) {
63
            $model->setParent($parent);
64
        }
65
66
        return $list;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $list also could return the type PDOStatement|array|string which is incompatible with the documented return type think\Collection.
Loading history...
67
    }
68
69
    /**
70
     * 预载入关联查询
71
     * @access public
72
     * @param  array    $resultSet   数据集
73
     * @param  string   $relation    当前关联名
74
     * @param  string   $subRelation 子关联名
75
     * @param  \Closure $closure     闭包
76
     * @return void
77
     */
78
    public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
79
    {
80
        $localKey = $this->localKey;
81
        $range    = [];
82
83
        foreach ($resultSet as $result) {
84
            // 获取关联外键列表
85
            if (isset($result->$localKey)) {
86
                $range[] = $result->$localKey;
87
            }
88
        }
89
90
        if (!empty($range)) {
91
            $where = [
92
                [$this->foreignKey, 'in', $range],
93
            ];
94
            $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure);
95
96
            // 关联属性名
97
            $attr = Loader::parseName($relation);
98
99
            // 关联数据封装
100
            foreach ($resultSet as $result) {
101
                $pk = $result->$localKey;
102
                if (!isset($data[$pk])) {
103
                    $data[$pk] = [];
104
                }
105
106
                foreach ($data[$pk] as &$relationModel) {
107
                    $relationModel->setParent(clone $result);
108
                }
109
110
                $result->setRelation($attr, $this->resultSetBuild($data[$pk]));
111
            }
112
        }
113
    }
114
115
    /**
116
     * 预载入关联查询
117
     * @access public
118
     * @param  Model    $result      数据对象
119
     * @param  string   $relation    当前关联名
120
     * @param  string   $subRelation 子关联名
121
     * @param  \Closure $closure     闭包
122
     * @return void
123
     */
124
    public function eagerlyResult(&$result, $relation, $subRelation, $closure)
125
    {
126
        $localKey = $this->localKey;
127
128
        if (isset($result->$localKey)) {
129
            $pk    = $result->$localKey;
130
            $where = [
131
                [$this->foreignKey, '=', $pk],
132
            ];
133
            $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure);
134
135
            // 关联数据封装
136
            if (!isset($data[$pk])) {
137
                $data[$pk] = [];
138
            }
139
140
            foreach ($data[$pk] as &$relationModel) {
141
                $relationModel->setParent(clone $result);
142
            }
143
144
            $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk]));
145
        }
146
    }
147
148
    /**
149
     * 关联统计
150
     * @access public
151
     * @param  Model    $result  数据对象
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 2 found
Loading history...
152
     * @param  \Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter name; 1 found
Loading history...
153
     * @param  string   $aggregate 聚合查询方法
154
     * @param  string   $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter name; 1 found
Loading history...
155
     * @param  string   $name 统计字段别名
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
156
     * @return integer
157
     */
158
    public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
159
    {
160
        $localKey = $this->localKey;
161
162
        if (!isset($result->$localKey)) {
163
            return 0;
164
        }
165
166
        if ($closure) {
0 ignored issues
show
introduced by
$closure is of type Closure, thus it always evaluated to true.
Loading history...
167
            $return = $closure($this->query);
168
            if ($return && is_string($return)) {
169
                $name = $return;
170
            }
171
        }
172
173
        return $this->query
174
            ->where($this->foreignKey, '=', $result->$localKey)
175
            ->$aggregate($field);
176
    }
177
178
    /**
179
     * 创建关联统计子查询
180
     * @access public
181
     * @param  \Closure $closure 闭包
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter name; 1 found
Loading history...
182
     * @param  string   $aggregate 聚合查询方法
0 ignored issues
show
Coding Style introduced by
Expected 6 spaces after parameter name; 1 found
Loading history...
183
     * @param  string   $field 字段
0 ignored issues
show
Coding Style introduced by
Expected 10 spaces after parameter name; 1 found
Loading history...
184
     * @param  string   $aggregateAlias 聚合字段别名
185
     * @return string
186
     */
187
    public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
188
    {
189
        if ($closure) {
0 ignored issues
show
introduced by
$closure is of type Closure, thus it always evaluated to true.
Loading history...
190
            $return = $closure($this->query);
191
192
            if ($return && is_string($return)) {
193
                $aggregateAlias = $return;
194
            }
195
        }
196
197
        return $this->query->alias($aggregate . '_table')
198
            ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
199
            ->fetchSql()
200
            ->$aggregate($field);
201
    }
202
203
    /**
204
     * 一对多 关联模型预查询
205
     * @access public
206
     * @param  array    $where       关联预查询条件
207
     * @param  string   $relation    关联名
208
     * @param  string   $subRelation 子关联
209
     * @param  \Closure $closure
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
210
     * @return array
211
     */
212
    protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = null)
213
    {
214
        $foreignKey = $this->foreignKey;
215
216
        $this->query->removeWhereField($this->foreignKey);
217
218
        // 预载入关联查询 支持嵌套预载入
219
        if ($closure) {
220
            $closure($this->query);
221
        }
222
223
        $list = $this->query->where($where)->with($subRelation)->select();
224
225
        // 组装模型数据
226
        $data = [];
227
228
        foreach ($list as $set) {
229
            $data[$set->$foreignKey][] = $set;
230
        }
231
232
        return $data;
233
    }
234
235
    /**
236
     * 保存(新增)当前关联数据对象
237
     * @access public
238
     * @param  mixed    $data       数据 可以使用数组 关联模型对象 和 关联对象的主键
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter name; 7 found
Loading history...
239
     * @param  boolean  $replace    是否自动识别更新和写入
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
240
     * @return Model|false
241
     */
242
    public function save($data, $replace = true)
243
    {
244
        $model = $this->make();
245
246
        return $model->replace($replace)->save($data) ? $model : false;
247
    }
248
249
    /**
250
     * 创建关联对象实例
251
     * @param array $data
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
252
     * @return Model
253
     */
254
    public function make($data = [])
255
    {
256
        if ($data instanceof Model) {
0 ignored issues
show
introduced by
$data is never a sub-type of think\Model.
Loading history...
257
            $data = $data->getData();
258
        }
259
260
        // 保存关联表数据
261
        $data[$this->foreignKey] = $this->parent->{$this->localKey};
262
263
        return new $this->model($data);
264
    }
265
266
    /**
267
     * 批量保存当前关联数据对象
268
     * @access public
269
     * @param  array|\think\Collection $dataSet   数据集
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 3 found
Loading history...
270
     * @param  boolean $replace 是否自动识别更新和写入
271
     * @return array|false
272
     */
273
    public function saveAll($dataSet, $replace = true)
274
    {
275
        $result = [];
276
277
        foreach ($dataSet as $key => $data) {
278
            $result[] = $this->save($data, $replace);
279
        }
280
281
        return empty($result) ? false : $result;
282
    }
283
284
    /**
285
     * 根据关联条件查询当前模型
286
     * @access public
287
     * @param  string  $operator 比较操作符
288
     * @param  integer $count    个数
289
     * @param  string  $id       关联表的统计字段
290
     * @param  string  $joinType JOIN类型
291
     * @return Query
292
     */
293
    public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
294
    {
295
        $table      = $this->query->getTable();
296
        $model      = basename(str_replace('\\', '/', get_class($this->parent)));
297
        $relation   = basename(str_replace('\\', '/', $this->model));
298
        $softDelete = $this->query->getOptions('soft_delete');
299
300
        return $this->parent->db()
301
            ->alias($model)
302
            ->field($model . '.*')
303
            ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType)
304
            ->when($softDelete, function ($query) use ($softDelete, $relation) {
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...
305
                $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
306
            })
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...
307
            ->group($relation . '.' . $this->foreignKey)
308
            ->having('count(' . $id . ')' . $operator . $count);
309
    }
310
311
    /**
312
     * 根据关联条件查询当前模型
313
     * @access public
314
     * @param  mixed     $where 查询条件(数组或者闭包)
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 1 found
Loading history...
315
     * @param  mixed     $fields 字段
316
     * @return Query
317
     */
318
    public function hasWhere($where = [], $fields = null)
319
    {
320
        $table    = $this->query->getTable();
321
        $model    = basename(str_replace('\\', '/', get_class($this->parent)));
322
        $relation = basename(str_replace('\\', '/', $this->model));
323
324
        if (is_array($where)) {
325
            $this->getQueryWhere($where, $relation);
326
        }
327
328
        $fields     = $this->getRelationQueryFields($fields, $model);
329
        $softDelete = $this->query->getOptions('soft_delete');
330
331
        return $this->parent->db()
332
            ->alias($model)
333
            ->group($model . '.' . $this->localKey)
334
            ->field($fields)
335
            ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey)
336
            ->when($softDelete, function ($query) use ($softDelete, $relation) {
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...
337
                $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
338
            })
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...
339
            ->where($where);
340
    }
341
342
    /**
343
     * 执行基础查询(仅执行一次)
344
     * @access protected
345
     * @return void
346
     */
347
    protected function baseQuery()
348
    {
349
        if (empty($this->baseQuery)) {
350
            if (isset($this->parent->{$this->localKey})) {
351
                // 关联查询带入关联条件
352
                $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
353
            }
354
355
            $this->baseQuery = true;
356
        }
357
    }
358
359
}
360