MyActiveTrait::createClosingRow()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 12
rs 10
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
     *
31
     * @var bool $is_logicDelete by default all deletes are logical deletes
32
     */
33
    public $is_logicDelete = true;
34
35
36
    // for updater & time & closer id
37
    public $userCreatedCol = 'user_created';
38
    public $userUpdatedCol = 'user_updated';
39
    public $userClosedCol = 'user_closed';
40
    public $timeCreatedCol = 'time_created';
41
    public $timeUpdatedCol = 'time_updated';
42
    public $timeClosedCol = 'time_closed';
43
44
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function save($runValidation = true, $attributeNames = null)
50
    {
51
        $userId = $this->userId();
52
        if ($this->isNewRecord) {
53
            $this->{$this->timeClosedCol} = $this->dateHelper->getEndOfTime();
54
            $this->{$this->userCreatedCol} = $userId;
55
            $this->{$this->timeCreatedCol} = $this->dateHelper->getDatetime6();
56
        }
57
58
        $this->{$this->userUpdatedCol} = $userId;
59
        $this->{$this->timeUpdatedCol} = $this->dateHelper->getDatetime6();
60
        return parent::save($runValidation, $attributeNames);
61
62
    }
63
64
    /**
65
     * @return int
66
     * @throws yii\base\InvalidConfigException
67
     * @deprecated use userId() instead
68
     */
69
    protected function getIdentityId()
70
    {
71
        $identity = Yii::$app->user->identity;
72
        if (is_null($identity)) {
73
            throw new yii\base\InvalidConfigException();
74
        }
75
        return (int) $identity->getId();
76
77
    }
78
79
    /**
80
     * Get an user id for the record manipulation
81
     * @return integer
82
     */
83
    private function userId()
84
    {
85
86
        if (Yii::$app instanceof yii\console\Application) {
87
            return 1;
88
        }
89
        if (!isset(Yii::$app->user) || empty(Yii::$app->user->identity)) {
90
            return 1;
91
        }
92
        $id = Yii::$app->user->id;
93
94
        if (empty($id)) {
95
            $id = 1;
96
        }
97
        return $id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $id also could return the type string which is incompatible with the documented return type integer.
Loading history...
98
99
    }
100
101
102
103
    /**
104
     * Return a label for the model eg for display lists, selections
105
     * this method must be overridden
106
     * @return string
107
     */
108
    public function label() {
109
        return "";
110
    }
111
112
    /**
113
     * Get Model name for views.
114
     * This method needs to be overridden
115
     * @return string Model display name
116
     */
117
    public static function modelName()
118
    {
119
        return Inflector::camel2words(StringHelper::basename(self::tableName()));
120
    }
121
122
    /**
123
     * Override delete function to make it logical delete
124
     * {@inheritdoc}
125
     */
126
    public function delete() {
127
        if ($this->is_logicDelete) {
128
            return $this->logicalDelete();
129
        }
130
        return parent::delete();
131
    }
132
133
    /**
134
     * @return bool
135
     * @throws yii\base\InvalidConfigException
136
     * @throws yii\base\UserException
137
     */
138
    private function logicalDelete() {
139
        $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

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

141
        $this->/** @scrutinizer ignore-call */ 
142
               setAttributes($this->oldAttributes);
Loading history...
142
143
        // delete logically
144
        if ($this->userUpdatedCol) {
145
            $this->{$this->userUpdatedCol} = $this->userId();
146
        }
147
        if ($this->userClosedCol) {
148
            $this->{$this->userClosedCol} = $this->userId();
149
        }
150
151
        if ($this->timeUpdatedCol) {
152
            $this->{$this->timeUpdatedCol} = $this->dateHelper->getDatetime6();
153
        }
154
155
        if ($this->timeClosedCol) {
156
            $this->{$this->timeClosedCol} = $this->dateHelper->getDatetime6();
157
        }
158
159
        // don't validate on deleting
160
        if ($this->save(false)) {
161
            $this->updateClosingTime(static::tableName());
162
            $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

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