Passed
Push — master ( 52dcbe...46e258 )
by Aleksandr
02:20
created

PivotTrait::storagePivot()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 6
nop 4
dl 0
loc 12
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace carono\yii2migrate\traits;
4
5
use yii\base\Model;
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
    protected $_storageAttributes = [];
20
21
    /**
22
     * @param string|ActiveRecord $pivotClass
23
     *
24
     * @return mixed
25
     */
26
    public function deletePivots($pivotClass)
27
    {
28
        return $pivotClass::deleteAll([$this->getMainPkField($this, $pivotClass) => $this->getMainPk()]);
29
    }
30
31
    /**
32
     * @param string $pivotClass
33
     *
34
     * @return ActiveRecord[]
35
     */
36
    public function getStoragePivots($pivotClass)
37
    {
38
        if (isset($this->_storage[$pivotClass])) {
39
            return $this->_storage[$pivotClass];
40
        } else {
41
            return [];
42
        }
43
    }
44
45
    /**
46
     * @return array
47
     */
48
    public function getPivotStorage()
49
    {
50
        return $this->_storage;
51
    }
52
53
    /**
54
     * @param ActiveRecord|null $model
55
     * @param string|ActiveRecord $pivotClass
56
     * @return array
57
     */
58
    private function getPivotCondition($model, $pivotClass)
59
    {
60
        $mainPk = $this->getPkField($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\traits\PivotTrait::getPkField(). ( Ignorable by Annotation )

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

60
        $mainPk = $this->getPkField(/** @scrutinizer ignore-type */ $this, $pivotClass);
Loading history...
61
        $condition = [$mainPk => $this->getMainPk()];
62
        if (!is_null($model)) {
63
            $slavePk = $this->getSlavePkField($model, $pivotClass);
64
            $condition[$slavePk] = $model->getAttribute($model->primaryKey()[0]);
65
        }
66
        return $condition;
67
    }
68
69
    /**
70
     * @param ActiveRecord $model
71
     * @param string|ActiveRecord $pivotClass
72
     * @param array $condition
73
     * @return ActiveRecord|null
74
     */
75
    public function getPivot($model, $pivotClass, $condition = [])
76
    {
77
        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...
78
    }
79
80
    /**
81
     * @param string|ActiveRecord $pivotClass
82
     * @param array $condition
83
     * @return ActiveRecord[]
84
     */
85
    public function getPivots($pivotClass, $condition = [])
86
    {
87
        return $this->findPivots($pivotClass)->andWhere($condition)->all();
88
    }
89
90
    /**
91
     * @param ActiveRecord $model
92
     * @param string|ActiveRecord $pivotClass
93
     * @return ActiveQuery
94
     */
95
    public function findPivot($model, $pivotClass)
96
    {
97
        return $pivotClass::find()->andWhere($this->getPivotCondition($model, $pivotClass));
98
    }
99
100
    /**
101
     * @param string|ActiveRecord $pivotClass
102
     * @return ActiveQuery
103
     */
104
    public function findPivots($pivotClass)
105
    {
106
        return $this->findPivot(null, $pivotClass);
107
    }
108
109
    /**
110
     * @param string $pivotClass
111
     */
112
    public function clearStorage($pivotClass)
113
    {
114
        unset($this->_storage[$pivotClass]);
115
    }
116
117
    /**
118
     * @param ActiveRecord[] $models
119
     * @param string $pivotClass
120
     * @param null $modelClass
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $modelClass is correct as it would always require null to be passed?
Loading history...
121
     */
122
    public function storagePivots($models, $pivotClass, $modelClass = null)
123
    {
124
        if (!is_array($models)) {
0 ignored issues
show
introduced by
The condition ! is_array($models) can never be true.
Loading history...
125
            $models = [$models];
126
        }
127
        foreach ($models as $model) {
128
            $this->storagePivot($model, $pivotClass, $modelClass);
129
        }
130
    }
131
132
    /**
133
     * @param ActiveRecord $model
134
     * @param ActiveRecord|string $pivotClass
135
     * @param ActiveRecord $modelClass
136
     *
137
     * @param array $pvAttributes
138
     * @throws \Exception
139
     */
140
    public function storagePivot($model, $pivotClass, $modelClass = null, $pvAttributes = [])
141
    {
142
        if (is_numeric($model) && $modelClass) {
0 ignored issues
show
introduced by
The condition is_numeric($model) && $modelClass can never be true.
Loading history...
143
            $model = $modelClass::findOne($model);
144
        } elseif (is_array($model)) {
145
            $model = \Yii::createObject($model);
146
        }
147
        if (!($model instanceof Model)) {
148
            throw new \Exception('Cannot determine or model not found');
149
        }
150
        $this->_storage[$pivotClass][] = $model;
151
        $this->_storageAttributes[$pivotClass][spl_object_hash($model)] = $pvAttributes;
152
    }
153
154
    public function getStoragePivotAttribute($model, $pivotClass)
155
    {
156
        return ArrayHelper::getValue($this->_storageAttributes, $pivotClass . '.' . spl_object_hash($model), []);
157
    }
158
159
    /**
160
     * @param bool $clear
161
     */
162
    public function savePivots($clear = false)
163
    {
164
        foreach ($this->getPivotStorage() as $pivotClass => $items) {
165
            if ($clear) {
166
                $this->deletePivots($pivotClass);
167
            }
168
            foreach ($items as $item) {
169
                $this->addPivot($item, $pivotClass);
170
            }
171
        }
172
    }
173
174
    /**
175
     * @param $model
176
     * @param $pivotClass
177
     * @param array $attributes
178
     * @return array|null|ActiveRecord
179
     * @throws \Exception
180
     */
181
    public function addPivot($model, $pivotClass, $attributes = [])
182
    {
183
        /**
184
         * @var ActiveRecord $pv
185
         */
186
        $pv = new $pivotClass;
187
        $attributes = $attributes ? $attributes : $this->getStoragePivotAttribute($model, $pivotClass);
188
        $condition = $this->getPivotCondition($model, $pivotClass);
189
        if ($find = (new ActiveQuery($pivotClass))->andWhere($condition)->one()) {
190
            if ($attributes) {
191
                $find->setAttributes($attributes, false);
192
                $find->save();
193
            }
194
            return $find;
195
        } else {
196
            $pv->setAttributes(array_merge($condition, $attributes), false);
197
            $pv->save();
198
            return $pv;
199
        }
200
    }
201
202
    /**
203
     * @param ActiveRecord $model
204
     * @param string|ActiveRecord $pivotClass
205
     * @return mixed
206
     */
207
    public function deletePivot($model, $pivotClass)
208
    {
209
        return $pivotClass::deleteAll([
210
            $this->getMainPkField($this, $pivotClass) => $this->getMainPk(),
211
            $this->getSlavePkField($model, $pivotClass) => $model->{$model->primaryKey()[0]}
212
        ]);
213
    }
214
215
216
    /**
217
     * @return mixed
218
     */
219
    protected function getMainPk()
220
    {
221
        /**
222
         * @var ActiveRecord $this
223
         */
224
        return $this->{$this->primaryKey()[0]};
225
    }
226
227
    /**
228
     * @param $model
229
     * @param string|ActiveRecord $pivotClass
230
     * @return string
231
     */
232
    protected function getMainPkField($model, $pivotClass)
233
    {
234
        /**
235
         * @var ActiveRecord $this
236
         */
237
        return $this->getPkField($model, $pivotClass);
238
    }
239
240
    /**
241
     * @param $model
242
     * @param string|ActiveRecord $pivotClass
243
     * @return string
244
     */
245
    protected function getSlavePkField($model, $pivotClass)
246
    {
247
        return $this->getPkField($model, $pivotClass, true);
248
    }
249
250
    /**
251
     * @param ActiveRecord $model
252
     * @param ActiveRecord|string $pivotClass
253
     * @param bool $slave
254
     * @return int|null|string
255
     */
256
    private function getPkField($model, $pivotClass, $slave = false)
257
    {
258
        // TODO: на данный момент не могу определить, какое поле является главным в сводной таблице
259
        // поэтому считаем, что первое по порядку - главное, второе - второстепенное
260
261
        if ($filed = $this->getPkFieldByModel($model, $pivotClass)) {
262
            return $filed;
263
        } else {
264
            $pks = self::getDb()->getTableSchema($pivotClass::tableName())->primaryKey;
265
            return $slave ? $pks[1] : $pks[0];
266
        }
267
    }
268
269
    /**
270
     * @param ActiveRecord $model
271
     * @param ActiveRecord|string $pivotClass
272
     * @return string
273
     */
274
    private function getPkFieldByModel($model, $pivotClass)
275
    {
276
        $pks = self::getDb()->getTableSchema($pivotClass::tableName())->primaryKey;
277
        $fks = self::formFkKeys(self::getDb()->getTableSchema($pivotClass::tableName())->foreignKeys);
278
        $fks = array_values(array_filter($fks, function ($data) use ($pks) {
279
            return in_array($data['field'], $pks);
280
        }));
281
282
        $table = preg_replace('#{{%([\w\d\-_]+)}}#', $model::getDb()->tablePrefix . "$1", $model::tableName());
283
284
        $field = null;
285
        foreach ($fks as $fk) {
286
            if ($fk['table'] == $table) {
287
                if ($field) {
288
                    return null;
289
                } else {
290
                    $field = $fk['field'];
291
                }
292
            }
293
        }
294
        return $field;
295
    }
296
297
    /**
298
     * @param $array
299
     * @return array
300
     */
301
    private static function formFkKeys($array)
302
    {
303
        $result = [];
304
        foreach ($array as $key => $data) {
305
            $result[$key] = [
306
                'table' => ArrayHelper::remove($data, 0),
307
                'field' => key($data),
308
                'reference' => current($data),
309
            ];
310
        }
311
        return $result;
312
    }
313
}