Passed
Push — master ( eae9c0...9cc093 )
by Tõnis
02:30
created

MyActiveTrait::logicalDelete()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 32
nop 0
dl 0
loc 29
rs 8.8337
c 0
b 0
f 0
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
        return Inflector::camel2words(StringHelper::basename(self::tableName()));
112
    }
113
114
    /**
115
     * Override delete function to make it logical delete
116
     * {@inheritdoc}
117
     */
118
    public function delete() {
119
        if ($this->is_logicDelete) {
120
            return $this->logicalDelete();
121
        }
122
        return parent::delete();
123
    }
124
125
    /**
126
     * @return bool
127
     * @throws yii\base\InvalidConfigException
128
     * @throws yii\base\UserException
129
     */
130
    private function logicalDelete() {
131
        $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

131
        $this->/** @scrutinizer ignore-call */ 
132
               beforeDelete();
Loading history...
132
        // don't put new data if deleting
133
        $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

133
        $this->/** @scrutinizer ignore-call */ 
134
               setAttributes($this->oldAttributes);
Loading history...
134
135
        // delete logically
136
        if ($this->userUpdatedCol) {
137
            $this->{$this->userUpdatedCol} = $this->getIdentityId(); ;
138
        }
139
        if ($this->userClosedCol) {
140
            $this->{$this->userClosedCol} = $this->getIdentityId(); ;
141
        }
142
143
        if ($this->timeUpdatedCol) {
144
            $this->{$this->timeUpdatedCol} = $this->dateHelper->getDatetime6();
145
        }
146
147
        if ($this->timeClosedCol) {
148
            $this->{$this->timeClosedCol} = $this->dateHelper->getDatetime6();
149
        }
150
151
        // don't validate on deleting
152
        if ($this->save(false)) {
153
            self::updateClosingTime(static::tableName());
154
            $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

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