Passed
Push — master ( 5a5857...6f4809 )
by Tõnis
02:31
created

MyActiveTrait   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 369
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 369
rs 8.64
c 0
b 0
f 0
wmc 47

23 Methods

Rating   Name   Duplication   Size   Complexity  
A updateClosingTime() 0 9 2
A getDateHelper() 0 3 1
A modelName() 0 4 1
A getIdentityId() 0 7 2
B bulkCopy() 0 28 6
A getTimeClosed() 0 3 1
A timeClosedCondition() 0 4 1
A find() 0 5 1
A save() 0 12 2
A userId() 0 9 4
A hasClosing() 0 3 1
A rules() 0 5 1
A getTimeUpdated() 0 3 1
A bulkDelete() 0 25 2
A createClosingRow() 0 12 2
A lastClosingTime() 0 12 3
A query() 0 4 1
A copy() 0 10 3
A label() 0 2 1
A getCount() 0 6 2
B delete() 0 35 7
A getTimeCreated() 0 3 1
A attributeLabels() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like MyActiveTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MyActiveTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace andmemasin\myabstract\traits;
4
5
use andmemasin\myabstract\Closing;
6
use yii;
7
use andmemasin\helpers\DateHelper;
8
use yii\db\ActiveQuery;
9
use yii\helpers\Inflector;
10
use yii\helpers\StringHelper;
11
use yii\db\Query;
12
13
/**
14
 * General code to be used in MyActiveRecord as well as User class
15
 * That can not extend MyActiveRecord
16
 *
17
 * @property string $timeCreated
18
 * @property string $timeUpdated
19
 * @property string $timeClosed
20
 * @property DateHelper $dateHelper
21
 *
22
 * @package andmemasin\myabstract
23
 * @author Tonis Ormisson <[email protected]>
24
 */
25
trait MyActiveTrait {
26
27
28
    /**
29
     *
30
     * @var bool $is_logicDelete by default all deletes are logical deletes
31
     */
32
    public $is_logicDelete = true;
33
34
35
    // for updater & time & closer id
36
    public $userCreatedCol = 'user_created';
37
    public $userUpdatedCol = 'user_updated';
38
    public $userClosedCol = 'user_closed';
39
    public $timeCreatedCol = 'time_created';
40
    public $timeUpdatedCol = 'time_updated';
41
    public $timeClosedCol = 'time_closed';
42
43
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function save($runValidation = true, $attributeNames = null)
49
    {
50
        $userId = $this->userId();
51
        if ($this->isNewRecord) {
52
            $this->{$this->timeClosedCol} = $this->dateHelper->getEndOfTime();
53
            $this->{$this->userCreatedCol} = $userId;
54
            $this->{$this->timeCreatedCol} = $this->dateHelper->getDatetime6();
55
        }
56
57
        $this->{$this->userUpdatedCol} = $userId;
58
        $this->{$this->timeUpdatedCol} = $this->dateHelper->getDatetime6();
59
        return parent::save($runValidation, $attributeNames);
60
61
    }
62
63
    /**
64
     * @return int
65
     * @throws yii\base\InvalidConfigException
66
     */
67
    protected function getIdentityId()
68
    {
69
        $identity = Yii::$app->user->identity;
70
        if (is_null($identity)) {
71
            throw new yii\base\InvalidConfigException();
72
        }
73
        return (int) $identity->getId();
74
75
    }
76
77
    /**
78
     * Get an user id for the record manipulation
79
     * @return integer
80
     */
81
    private function userId()
82
    {
83
        if (Yii::$app instanceof yii\console\Application) {
84
            return 1;
85
        }
86
        if (!isset(Yii::$app->user) || empty(Yii::$app->user->identity)) {
87
            return 1;
88
        }
89
        return $this->getIdentityId();
90
91
    }
92
93
94
95
    /**
96
     * Return a label for the model eg for display lists, selections
97
     * this method must be overridden
98
     * @return string
99
     */
100
    public function label() {
101
        return "";
102
    }
103
104
    /**
105
     * Get Model name for views.
106
     * This method needs to be overridden
107
     * @return string Model display name
108
     */
109
    public static function modelName()
110
    {
111
        // FIXME this is not OK
112
        return Inflector::camel2words(StringHelper::basename(self::tableName()));
113
    }
114
115
    /**
116
     * Override delete function to make it logical delete
117
     * {@inheritdoc}
118
     */
119
    public function delete() {
120
        if ($this->is_logicDelete) {
121
            $this->beforeDelete();
0 ignored issues
show
Bug introduced by
It seems like beforeDelete() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

121
            $this->/** @scrutinizer ignore-call */ 
122
                   beforeDelete();
Loading history...
122
            // don't put new data if deleting
123
            $this->setAttributes($this->oldAttributes);
0 ignored issues
show
Bug introduced by
It seems like setAttributes() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

123
            $this->/** @scrutinizer ignore-call */ 
124
                   setAttributes($this->oldAttributes);
Loading history...
124
125
            // delete logically
126
            if ($this->userUpdatedCol) {
127
                $this->{$this->userUpdatedCol} = $this->getIdentityId(); ;
128
            }
129
            if ($this->userClosedCol) {
130
                $this->{$this->userClosedCol} = $this->getIdentityId(); ;
131
            }
132
133
            if ($this->timeUpdatedCol) {
134
                $this->{$this->timeUpdatedCol} = $this->dateHelper->getDatetime6();
135
            }
136
137
            if ($this->timeClosedCol) {
138
                $this->{$this->timeClosedCol} = $this->dateHelper->getDatetime6();
139
            }
140
141
            // don't validate on deleting
142
            if ($this->save(false)) {
143
                self::updateClosingTime(static::tableName());
144
                $this->afterDelete();
0 ignored issues
show
Bug introduced by
It seems like afterDelete() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

144
                $this->/** @scrutinizer ignore-call */ 
145
                       afterDelete();
Loading history...
145
                return true;
146
            } else {
147
                throw new yii\base\UserException('Error deleting model');
148
            }
149
150
        } else {
151
            // otherwise regular delete
152
            parent::delete();
153
            return true;
154
        }
155
156
    }
157
158
159
    public static function bulkCopy($objects, $replaceParams) {
160
        /**
161
         * @var yii\db\ActiveRecord $model
162
         */
163
        $model = new static;
164
        if (!empty($objects)) {
165
            $rows = [];
166
            $cols = [];
167
            foreach ($objects as $object) {
168
                if (!empty($object->attributes)) {
169
                    $row = $object->attributes;
170
                    $cols = $model->attributes();
171
                    foreach ($replaceParams as $key =>$value) {
172
                        // remove primary keys (assuming auto-increment)
173
                        foreach ($model->primaryKey as $pk) {
174
                            unset($row[$pk]);
175
                        }
176
                        // remove pk fields from cols
177
                        $cols = array_diff($cols, $model->primaryKey);
178
                        $row[$key] = $value;
179
                    }
180
                    $rows[] = $row;
181
                } else {
182
                    throw new yii\base\InvalidArgumentException('Missing object attributes in ' . get_called_class() . ' ' . __FUNCTION__);
183
                }
184
185
            }
186
            \Yii::$app->db->createCommand()->batchInsert(parent::tableName(), $cols, $rows)->execute();
187
188
        }
189
    }
190
191
    /**
192
     * Bulk delete (logic) objects based on the conditions set  in $params
193
     * NB! this does NOT call before/after delete
194
     * @param array $params Array with the WHERE conditions as per QueryBuilder eg ['id'=>1] or.. ['>','id',3]
195
     */
196
    public static function bulkDelete($params) {
197
        $dateHelper = new DateHelper();
198
199
        /**
200
         * @var \yii\db\ActiveRecord
201
         */
202
        $model = new static;
203
        if (!empty($params)) {
204
205
            $baseParams = [
206
                $model->timeClosedCol=>$dateHelper->getDatetime6(),
207
                $model->userClosedCol => (new static)->getIdentityId(),
208
                $model->timeUpdatedCol=>$dateHelper->getDatetime6(),
209
                $model->userUpdatedCol =>(new static)->getIdentityId(),
210
            ];
211
212
            $conditions = [];
213
            $conditions[] = 'and';
214
            $conditions[] = ['>', static::tableName() . ".`" . $model->timeClosedCol . '`', $dateHelper->getDatetime6()];
215
            $conditions[] = $params;
216
            \Yii::$app->db->createCommand()->update(parent::tableName(), $baseParams, $conditions)->execute();
217
            self::updateClosingTime(static::tableName());
218
219
        } else {
220
            throw new yii\base\InvalidArgumentException('No conditions defined for ' . get_called_class() . ' ' . __FUNCTION__);
221
        }
222
223
224
225
    }
226
227
228
    /**
229
     * {@inheritdoc}
230
     */
231
    public function rules() {
232
        return [
233
            [[$this->userCreatedCol, $this->userUpdatedCol, $this->timeCreatedCol, $this->timeUpdatedCol, $this->timeClosedCol], 'required'],
234
            [[$this->userCreatedCol, $this->userUpdatedCol, $this->userClosedCol], 'integer'],
235
            [[$this->timeCreatedCol, $this->timeUpdatedCol, $this->timeClosedCol], 'safe'],
236
        ];
237
    }
238
    /**
239
     * {@inheritdoc}
240
     */
241
    public function attributeLabels() {
242
        return [
243
            $this->userCreatedCol => Yii::t('app', 'Created by'),
244
            $this->userUpdatedCol => Yii::t('app', 'Updated by'),
245
            $this->userClosedCol => Yii::t('app', 'Closed by'),
246
            $this->timeCreatedCol => Yii::t('app', 'Created at'),
247
            $this->timeUpdatedCol => Yii::t('app', 'Updated at'),
248
            $this->timeClosedCol => Yii::t('app', 'Closed at'),
249
        ];
250
    }
251
252
    /**
253
     * Only returns models that have not been closed
254
     * {@inheritdoc}
255
     * @return ActiveQuery the newly created [[ActiveQuery]] instance.
256
     */
257
    public static function find() {
258
        $child = new static;
259
        $query = parent::find()
260
            ->andFilterWhere($child->timeClosedCondition());
261
        return  $query;
262
    }
263
264
    public function timeClosedCondition()
265
    {
266
        $lastClosingTime = self::lastClosingTime(static::tableName());
267
        return ['>', static::tableName() . ".`" . $this->timeClosedCol . '`', $lastClosingTime];
268
    }
269
270
271
    public static function getCount($filter = null) {
272
        $query = self::find();
273
        if ($filter) {
274
            $query->andFilterWhere($filter);
275
        }
276
        return $query->count();
277
    }
278
279
    /**
280
     * a general query that adds the UserStrings filter on top of original query
281
     * @return Query
282
     */
283
    public static function query() {
284
        $child = new static;
285
        $dateHelper = new DateHelper();
286
        return (new Query())->andFilterWhere(['>', parent::tableName() . ".`" . $child->timeClosedCol . '`', $dateHelper->getDatetime6()]);
287
    }
288
289
    /**
290
     * Copy a model to a new model while replacing some params with new values
291
     * @param \yii\db\ActiveRecord $model
292
     * @param array $map map of old model attribute as keys and new values as values
293
     * @return bool|static
294
     * @throws yii\base\UserException
295
     */
296
    public static function copy($model, $map) {
297
        $newModel = new static;
298
        $newModel->attributes = $model->attributes;
0 ignored issues
show
Bug Best Practice introduced by
The property attributes does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
299
        foreach ($map as $key => $value) {
300
            $newModel->{$key} = $value;
301
        }
302
        if ($newModel->save()) {
303
            return $newModel;
304
        }
305
        throw new yii\base\UserException('Error copying model');
306
    }
307
308
    /**
309
     * @param string $tableName
310
     * @return mixed|string
311
     */
312
    private static function lastClosingTime($tableName) {
313
        $dateHelper = new DateHelper();
314
315
        if (!self::hasClosing($tableName)) {
316
            self::createClosingRow($tableName);
317
        }
318
        /** @var Closing $closing */
319
        $closing = Closing::findOne($tableName);
320
        if ($closing) {
0 ignored issues
show
introduced by
$closing is of type andmemasin\myabstract\Closing, thus it always evaluated to true. If $closing can have other possible types, add them to src/traits/MyActiveTrait.php:318
Loading history...
321
            return $closing->last_closing_time;
322
        }
323
        return $dateHelper->getDatetime6();
324
    }
325
326
    /**
327
     * @param string $tableName
328
     * @return bool
329
     */
330
    private static function hasClosing($tableName){
331
        $closing = Closing::findOne($tableName);
332
        return !($closing == null);
333
    }
334
335
    /**
336
     * @param $tableName
337
     * @return Closing
338
     */
339
    private static function createClosingRow($tableName) {
340
341
        if (!self::hasClosing($tableName)) {
342
            $dateHelper = new DateHelper();
343
            $closing = new Closing([
344
                'table_name'=>$tableName,
345
                'last_closing_time' => $dateHelper->getDatetime6(),
346
            ]);
347
            $closing->save();
348
            return $closing;
349
        }
350
        return null;
351
    }
352
353
    private static function updateClosingTime($tableName) {
354
        if (!self::hasClosing($tableName)) {
355
            self::createClosingRow($tableName);
356
        }
357
        /** @var Closing $closing */
358
        $closing = Closing::findOne($tableName);
359
        $dateHelper = new DateHelper();
360
        $closing->last_closing_time = $dateHelper->getDatetime6();
361
        $closing->save();
362
    }
363
364
    /**
365
     * @return string
366
     */
367
    public function getTimeCreated()
368
    {
369
        return $this->{$this->timeCreatedCol};
370
    }
371
372
    /**
373
     * @return string
374
     */
375
    public function getTimeUpdated()
376
    {
377
        return $this->{$this->timeUpdatedCol};
378
    }
379
380
    /**
381
     * @return string
382
     */
383
    public function getTimeClosed()
384
    {
385
        return $this->{$this->timeClosedCol};
386
    }
387
388
    /**
389
     * @return DateHelper
390
     */
391
    public function getDateHelper()
392
    {
393
        return new DateHelper();
394
    }
395
396
397
}
398