Test Failed
Branch master (1fbe3c)
by Aleksandr
02:14
created

PivotTrait::savePivots()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 4
nc 5
nop 1
1
<?php
2
3
namespace carono\yii2migrate\traits;
4
5
use carono\yii2migrate\helpers\SchemaHelper;
6
use yii\db\ActiveQuery;
7
use yii\db\ActiveRecord;
8
use yii\helpers\ArrayHelper;
9
10
/**
11
 * Trait PivotTrait
12
 *
13
 * @package carono\yii2migrate\traits
14
 * @mixin ActiveRecord
15
 */
16
trait PivotTrait
17
{
18
    protected $_storage = [];
19
20
    /**
21
     * @param string|ActiveRecord $pivotClass
22
     *
23
     * @return mixed
24
     */
25
    public function deletePivots($pivotClass)
26
    {
27
        return $pivotClass::deleteAll([$this->getPivotMainPkField($this, $pivotClass) => $this->getMainPk()]);
0 ignored issues
show
Bug introduced by
$this of type carono\yii2migrate\traits\PivotTrait is incompatible with the type yii\db\ActiveRecord expected by parameter $model of carono\yii2migrate\trait...::getPivotMainPkField(). ( Ignorable by Annotation )

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

27
        return $pivotClass::deleteAll([$this->getPivotMainPkField(/** @scrutinizer ignore-type */ $this, $pivotClass) => $this->getMainPk()]);
Loading history...
28
    }
29
30
    /**
31
     * @param string $pivotClass
32
     *
33
     * @return ActiveRecord[]
34
     */
35
    public function getStoragePivots($pivotClass)
36
    {
37
        if (isset($this->_storage[$pivotClass])) {
38
            return array_values($this->_storage[$pivotClass]);
39
        }
40
41
        return [];
42
    }
43
44
    /**
45
     * @return array
46
     */
47
    public function getPivotStorage()
48
    {
49
        return $this->_storage;
50
    }
51
52
    /**
53
     * @param ActiveRecord|null $model
54
     * @param string|ActiveRecord $pivotClass
55
     * @return array
56
     */
57
    private function getPivotCondition($model, $pivotClass)
58
    {
59
        $mainPk = $this->getPivotMainPkField($this, $pivotClass);
0 ignored issues
show
Bug introduced by
$this of type carono\yii2migrate\traits\PivotTrait is incompatible with the type yii\db\ActiveRecord expected by parameter $model of carono\yii2migrate\trait...::getPivotMainPkField(). ( Ignorable by Annotation )

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

59
        $mainPk = $this->getPivotMainPkField(/** @scrutinizer ignore-type */ $this, $pivotClass);
Loading history...
60
        $condition = [$mainPk => $this->getMainPk()];
61
        if ($model !== null) {
62
            $slavePk = $this->getPivotSlavePkField($model, $pivotClass);
63
            $condition[$slavePk] = $model->getAttribute($model->primaryKey()[0]);
64
        }
65
        return $condition;
66
    }
67
68
    /**
69
     * @param ActiveRecord $model
70
     * @param string|ActiveRecord $pivotClass
71
     * @param array $condition
72
     * @return ActiveRecord|null
73
     */
74
    public function getPivot($model, $pivotClass, $condition = [])
75
    {
76
        return $this->findPivot($model, $pivotClass)->andWhere($condition)->one();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findPivot(...here($condition)->one() also could return the type array which is incompatible with the documented return type null|yii\db\ActiveRecord.
Loading history...
77
    }
78
79
    /**
80
     * @param string|ActiveRecord $pivotClass
81
     * @param array $condition
82
     * @return ActiveRecord[]
83
     */
84
    public function getPivots($pivotClass, $condition = [])
85
    {
86
        return $this->findPivots($pivotClass)->andWhere($condition)->all();
87
    }
88
89
    /**
90
     * @param ActiveRecord $model
91
     * @param string|ActiveRecord $pivotClass
92
     * @return ActiveQuery
93
     */
94
    public function findPivot($model, $pivotClass)
95
    {
96
        return $pivotClass::find()->andWhere($this->getPivotCondition($model, $pivotClass));
97
    }
98
99
    /**
100
     * @param string|ActiveRecord $pivotClass
101
     * @return ActiveQuery
102
     */
103
    public function findPivots($pivotClass)
104
    {
105
        return $this->findPivot(null, $pivotClass);
106
    }
107
108
    /**
109
     * @param string $pivotClass
110
     */
111
    public function clearStorage($pivotClass)
112
    {
113
        unset($this->_storage[$pivotClass]);
114
    }
115
116
    /**
117
     * @param ActiveRecord[] $models
118
     * @param string $pivotClass
119
     * @param array $attributes
120
     */
121
    public function storagePivots($models, $pivotClass, $attributes = [])
122
    {
123
        foreach ((array)$models as $model) {
124
            $this->storagePivot($model, $pivotClass, $attributes);
125
        }
126
    }
127
128
    /**
129
     * @param ActiveRecord $model
130
     * @param string $pivotClass
131
     * @param array $attributes
132
     */
133
    public function storagePivot($model, $pivotClass, $attributes = [])
134
    {
135
        $this->_storage[$pivotClass][spl_object_hash($model)] = ['model' => $model, 'attributes' => $attributes];
136
    }
137
138
    public function getStoragePivotAttribute($model, $pivotClass)
139
    {
140
        return ArrayHelper::getValue($this->_storage, $pivotClass . '.' . spl_object_hash($model) . '.attributes', []);
141
    }
142
143
    /**
144
     * @param bool $clear
145
     */
146
    public function savePivots($clear = false)
147
    {
148
        foreach ($this->getPivotStorage() as $pivotClass => $items) {
149
            if ($clear) {
150
                $this->deletePivots($pivotClass);
151
            }
152
            foreach ($items as $item) {
153
                $this->addPivot($item['model'], $pivotClass, $item['attributes']);
154
            }
155
        }
156
    }
157
158
    /**
159
     * @param $model
160
     * @param $pivotClass
161
     * @param array $attributes
162
     * @return array|null|ActiveRecord
163
     * @throws \Exception
164
     */
165
    public function addPivot($model, $pivotClass, $attributes = [])
166
    {
167
        /**
168
         * @var ActiveRecord $pv
169
         */
170
        $pv = new $pivotClass;
171
        $attributes = $attributes ? $attributes : $this->getStoragePivotAttribute($model, $pivotClass);
172
        $condition = $this->getPivotCondition($model, $pivotClass);
173
        if ($find = (new ActiveQuery($pivotClass))->andWhere($condition)->one()) {
174
            if ($attributes) {
175
                $find->setAttributes($attributes, false);
176
                $find->save();
177
            }
178
            return $find;
179
        }
180
181
        $pv->setAttributes(array_merge($condition, $attributes), false);
182
        $pv->save();
183
        return $pv;
184
    }
185
186
    /**
187
     * @param ActiveRecord $model
188
     * @param string|ActiveRecord $pivotClass
189
     * @return mixed
190
     */
191
    public function deletePivot($model, $pivotClass)
192
    {
193
        return $pivotClass::deleteAll([
194
            $this->getPivotMainPkField($this, $pivotClass) => $this->getMainPk(),
0 ignored issues
show
Bug introduced by
$this of type carono\yii2migrate\traits\PivotTrait is incompatible with the type yii\db\ActiveRecord expected by parameter $model of carono\yii2migrate\trait...::getPivotMainPkField(). ( Ignorable by Annotation )

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

194
            $this->getPivotMainPkField(/** @scrutinizer ignore-type */ $this, $pivotClass) => $this->getMainPk(),
Loading history...
195
            $this->getPivotSlavePkField($model, $pivotClass) => $model->{$model->primaryKey()[0]}
196
        ]);
197
    }
198
199
200
    /**
201
     * @return mixed
202
     */
203
    protected function getMainPk()
204
    {
205
        /**
206
         * @var ActiveRecord $this
207
         */
208
        return $this->{$this->primaryKey()[0]};
209
    }
210
211
    /**
212
     * @param ActiveRecord $model
213
     * @param string|ActiveRecord $pivotClass
214
     * @return string
215
     */
216
    protected function getPivotMainPkField($model, $pivotClass)
217
    {
218
        return $this->getPivotPkField($model, $pivotClass, false);
219
    }
220
221
    /**
222
     * @param $model
223
     * @param string|ActiveRecord $pivotClass
224
     * @return string
225
     */
226
    protected function getPivotSlavePkField($model, $pivotClass)
227
    {
228
        return $this->getPivotPkField($model, $pivotClass, true);
229
    }
230
231
    /**
232
     * @param ActiveRecord $model
233
     * @param ActiveRecord|string $pivotClass
234
     * @param bool $slave
235
     * @return int|null|string
236
     */
237
    private function getPivotPkField($model, $pivotClass, $slave = false)
238
    {
239
        if ($field = $this->getPkFieldByModel($model, $pivotClass)) {
240
            return $field;
241
        }
242
243
        $pks = self::getDb()->getTableSchema($pivotClass::tableName())->primaryKey;
244
        return $slave ? $pks[1] : $pks[0];
245
    }
246
247
    /**
248
     * @param ActiveRecord $model
249
     * @param ActiveRecord|string $pivotClass
250
     * @return string
251
     */
252
    private function getPkFieldByModel($model, $pivotClass)
253
    {
254
        $pks = self::getDb()->getTableSchema($pivotClass::tableName())->primaryKey;
255
        $fks = self::formFkKeys(self::getDb()->getTableSchema($pivotClass::tableName())->foreignKeys);
256
        $fks = array_values(array_filter($fks, function ($data) use ($pks) {
257
            return in_array($data['field'], $pks, true);
258
        }));
259
        $table = SchemaHelper::expandTablePrefix($model::tableName(), $model::getDb()->tablePrefix);
260
        $field = null;
261
        foreach ($fks as $fk) {
262
            if ($fk['table'] === $table) {
263
                if ($field) {
264
                    return null;
265
                }
266
267
                $field = $fk['field'];
268
            }
269
        }
270
        return $field;
271
    }
272
273
    /**
274
     * @param $array
275
     * @return array
276
     */
277
    private static function formFkKeys($array)
278
    {
279
        $result = [];
280
        foreach ($array as $key => $data) {
281
            $result[$key] = [
282
                'table' => ArrayHelper::remove($data, 0),
283
                'field' => key($data),
284
                'reference' => current($data),
285
            ];
286
        }
287
        return $result;
288
    }
289
}