Completed
Push — master ( 9adc0b...e46bc3 )
by vistart
09:07
created

TimestampTrait::currentUtcDatetime()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4.125

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 3
cts 6
cp 0.5
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 0
crap 4.125
1
<?php
2
3
/**
4
 *   _   __ __ _____ _____ ___  ____  _____
5
 *  | | / // // ___//_  _//   ||  __||_   _|
6
 *  | |/ // /(__  )  / / / /| || |     | |
7
 *  |___//_//____/  /_/ /_/ |_||_|     |_|
8
 * @link https://vistart.me/
9
 * @copyright Copyright (c) 2016 - 2017 vistart
10
 * @license https://vistart.me/license/
11
 */
12
13
namespace rhosocial\base\models\traits;
14
15
use Closure;
16
use yii\base\ModelEvent;
17
use yii\behaviors\TimestampBehavior;
18
19
/**
20
 * Entity features concerning timestamp.
21
 * @property-read array $timestampBehaviors
22
 * @property-read string|int createdAt
23
 * @property-read string|int updatedAt
24
 * @property-read array $createdAtRules
25
 * @property-read array $updatedAtRules
26
 * @property-read boolean isExpired
27
 * @property int|false expiredAfter the expiration duration in seconds, or false if not expired.
28
 * @version 1.0
29
 * @author vistart <[email protected]>
30
 */
31
trait TimestampTrait
32
{
33
    /**
34
     * @var string|false the attribute that receive datetime value
35
     * Set this property to false if you do not want to record the creation time.
36
     */
37
    public $createdAtAttribute = 'created_at';
38
39
    /**
40
     * @var string|false the attribute that receive datetime value.
41
     * Set this property to false if you do not want to record the update time.
42
     */
43
    public $updatedAtAttribute = 'updated_at';
44
45
    /**
46
     * @var string|false the attribute that determine when this entity expire.
47
     * If this entity does not expire, set to false.
48
     */
49
    public $expiredAfterAttribute = false;
50
51
    /**
52
     * @var integer Determine the format of timestamp.
53
     */
54
    public $timeFormat = 0;
55
    public static $timeFormatDatetime = 0;
56
    public static $timeFormatTimestamp = 1;
57
    public static $initDatetime = '1970-01-01 00:00:00';
58
    public static $initTimestamp = 0;
59
    public $timeType = 1;
60
    public static $timeTypeUtc = 0;
61
    public static $timeTypeLocal = 1;
62
    public static $timeTypes = [
63
        0 => 'GMT',
64
        1 => 'local',
65
    ];
66
67
    /**
68
     * @var Closure
69
     */
70
    public $expiredRemovingCallback;
71
    public static $eventExpiredRemoved = 'expiredRemoved';
72
73
    /**
74
     * Check this entity whether expired.
75
     * This feature require creation time. If creation time didn't record, false
76
     * is returned.
77
     * This feature also need expiration duration. If expiration duration didn't
78
     * record, false is returned.
79
     * @return boolean
80
     */
81 219
    public function getIsExpired()
82
    {
83 219
        $createdAt = $this->getCreatedAt();
84 219
        if ($this->getExpiredAfter() === false || $createdAt === null) {
85 211
            return false;
86
        }
87 11
        if ($this->timeType == static::$timeTypeLocal) {
88 11
            return $this->offsetDatetime($this->currentDatetime(), -$this->getExpiredAfter()) > $createdAt;
89
        } elseif ($this->timeType == static::$timeTypeUtc) {
90
            return $this->offsetUtcDatetime($this->currentUtcDatetime(), -$this->getExpiredAfter()) > $createdAt;
91
        }
92
        return false;
93
    }
94
95
    /**
96
     * Remove myself if expired.
97
     * The `expiredRemovingCallback` will be called before removing itself,
98
     * then it would trigger `static::$eventExpiredRemoved` event, and attach
99
     * the removing results.
100
     * @return boolean
101
     */
102 214
    public function removeIfExpired()
103
    {
104 214
        if ($this->getIsExpired() && !$this->getIsNewRecord()) {
0 ignored issues
show
Bug introduced by
It seems like getIsNewRecord() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
105 11
            if (($this->expiredRemovingCallback instanceof Closure || is_array($this->expiredRemovingCallback)) && is_callable($this->expiredRemovingCallback)) {
106 3
                call_user_func($this->expiredRemovingCallback, $this);
107
            }
108 11
            $result = $this->removeSelf();
109 11
            $this->trigger(static::$eventExpiredRemoved, new ModelEvent(['data' => ['result' => $result]]));
0 ignored issues
show
Bug introduced by
It seems like trigger() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
110 11
            return true;
111
        }
112 203
        return false;
113
    }
114
115
    /**
116
     * Remove self.
117
     * You can override this method for implementing more complex features.
118
     * @see delete()
119
     * @return integer
120
     */
121 7
    public function removeSelf()
122
    {
123 7
        return $this->delete();
0 ignored issues
show
Bug introduced by
It seems like delete() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
124
    }
125
126
    /**
127
     * We recommened you attach this event when after finding this active record.
128
     * @param ModelEvent $event
129
     * @return boolean
130
     */
131 214
    public function onRemoveExpired($event)
132
    {
133 214
        return $event->sender->removeIfExpired();
134
    }
135
136
    /**
137
     * Get the current date & time in format of "Y-m-d H:i:s" or timestamp.
138
     * You can override this method to customize the return value.
139
     * @param ModelEvent $event
140
     * @return string Date & Time.
141
     */
142 342
    public static function getCurrentDatetime($event)
143
    {
144 342
        $sender = $event->sender;
145
        /* @var $sender static */
146 342
        if ($sender->timeType == static::$timeTypeUtc) {
147 1
            return $sender->currentUtcDatetime();
148 341
        } elseif ($sender->timeType == static::$timeTypeLocal) {
149 341
            return $sender->currentDatetime();
150
        }
151
        return null;
152
    }
153
154
    /**
155
     * Get current date & time, by current time format.
156
     * @return string|int Date & time string if format is datetime, or timestamp.
157
     */
158 342
    public function currentDatetime()
159
    {
160 342
        if ($this->timeFormat === static::$timeFormatDatetime) {
161 334
            return date('Y-m-d H:i:s');
162
        }
163 9
        if ($this->timeFormat === static::$timeFormatTimestamp) {
164 9
            return time();
165
        }
166 1
        return null;
167
    }
168
169
    /**
170
     * Get current Greenwich date & time (UTC), by current time format.
171
     * @return string|int Date & time string if format is datetime, or timestamp.
172
     */
173 1
    public function currentUtcDatetime()
174
    {
175 1
        if ($this->timeFormat === static::$timeFormatDatetime) {
176 1
            return gmdate('Y-m-d H:i:s');
177
        }
178
        if ($this->timeFormat === static::$timeFormatTimestamp) {
179
            return gmmktime();
180
        }
181
        return null;
182
    }
183
184
    /**
185
     * Get offset date & time, by current time format.
186
     * @param string|int $time Date &time string or timestamp.
187
     * @param int $offset Offset in seconds.
188
     * @return string|int Date & time string if format is datetime, or timestamp.
189
     */
190 13
    public function offsetDatetime($time = null, $offset = 0)
191
    {
192 13
        if ($this->timeFormat === static::$timeFormatDatetime) {
193 12
            return date('Y-m-d H:i:s', strtotime(($offset >= 0 ? "+$offset" : $offset) . " seconds", is_string($time) ? strtotime($time) : (is_int($time) ? $time : time())));
194
        }
195 2
        if ($this->timeFormat === static::$timeFormatTimestamp) {
196 2
            return (is_int($time) ? $time : time()) + $offset;
197
        }
198 1
        return null;
199
    }
200
201
    /**
202
     * Get offset date & time relative to Greenwich time(UTC), by current time format.
203
     * @param string|int $time Date &time string or timestamp.
204
     * @param int $offset Offset in seconds.
205
     * @return string|int Date & time string if format is datetime, or timestamp.
206
     */
207
    public function offsetUtcDatetime($time = null, $offset = 0)
208
    {
209
        if ($this->timeFormat === static::$timeFormatDatetime) {
210
            return gmdate('Y-m-d H:i:s', strtotime(($offset >= 0 ? "+$offset" : $offset) . " seconds", is_string($time) ? strtotime($time) : (is_int($time) ? $time : gmmktime())));
211
        }
212
        if ($this->timeFormat === static::$timeFormatTimestamp) {
213
            return (is_int($time) ? $time : gmmktime()) + $offset;
214
        }
215
        return null;
216
    }
217
218
    /**
219
     * Get init date & time in format of "Y-m-d H:i:s" or timestamp.
220
     * @param ModelEvent $event
221
     * @return string|int
222
     */
223 23
    public static function getInitDatetime($event)
224
    {
225 23
        $sender = $event->sender;
226 23
        return $sender->initDatetime();
227
    }
228
229
    /**
230
     * Get init date & time, by current time format.
231
     * @return string|int Date & time string if format is datetime, or timestamp.
232
     */
233 24
    public function initDatetime()
234
    {
235 24
        if ($this->timeFormat === static::$timeFormatDatetime) {
236 24
            return static::$initDatetime;
237
        }
238 1
        if ($this->timeFormat === static::$timeFormatTimestamp) {
239 1
            return static::$initTimestamp;
240
        }
241 1
        return null;
242
    }
243
244
    /**
245
     * Check whether the attribute is init datetime.
246
     * @param mixed $attribute
247
     * @return boolean
248
     */
249 7
    protected function isInitDatetime($attribute)
250
    {
251 7
        if ($this->timeFormat === static::$timeFormatDatetime) {
252 7
            return $attribute == static::$initDatetime || $attribute == null;
253
        }
254
        if ($this->timeFormat === static::$timeFormatTimestamp) {
255
            return $attribute == static::$initTimestamp || $attribute == null;
256
        }
257
        return false;
258
    }
259
260
    /**
261
     * Get the current date & time in format of "Y-m-d H:i:s".
262
     * This method is ONLY used for being triggered by event. DO NOT call,
263
     * override or modify it directly, unless you know the consequences.
264
     * @param ModelEvent $event
265
     * @return string Date & Time.
266
     */
267 342
    public function onUpdateCurrentDatetime($event)
268
    {
269 342
        return static::getCurrentDatetime($event);
270
    }
271
272
    /**
273
     * Behaviors associated with timestamp.
274
     * @return array behaviors
275
     */
276 380
    public function getTimestampBehaviors()
277
    {
278
        return [
279
            [
280
                'class' => TimestampBehavior::class,
281 380
                'createdAtAttribute' => $this->createdAtAttribute,
282 380
                'updatedAtAttribute' => $this->updatedAtAttribute,
283 380
                'value' => [$this, 'onUpdateCurrentDatetime'],
284
            ]
285 380
        ];
286
    }
287
288
    /**
289
     * Get creation time.
290
     * @return string timestamp
291
     */
292 242
    public function getCreatedAt()
293
    {
294 242
        $createdAtAttribute = $this->createdAtAttribute;
295 242
        if (!is_string($createdAtAttribute) || empty($createdAtAttribute)) {
296 9
            return null;
297
        }
298 233
        return $this->$createdAtAttribute;
299
    }
300
301
    /**
302
     * Get rules associated with createdAtAttribute.
303
     * The default rule is safe. Because the [[TimestampBehavior]] will attach
304
     * the creation time automatically.
305
     * Under normal circumstances is not recommended to amend.
306
     * If `createdAtAttribute` is not specified, the empty array will be given.
307
     * @return array rules
308
     */
309 353
    public function getCreatedAtRules()
310
    {
311 353
        if (!is_string($this->createdAtAttribute) || empty($this->createdAtAttribute)) {
312 9
            return [];
313
        }
314
        return [
315 346
            [[$this->createdAtAttribute], 'safe'],
316
        ];
317
    }
318
319
    /**
320
     * Get update time.
321
     * @return string timestamp
322
     */
323 35
    public function getUpdatedAt()
324
    {
325 35
        $updatedAtAttribute = $this->updatedAtAttribute;
326 35
        if (!is_string($updatedAtAttribute) || empty($updatedAtAttribute)) {
327 7
            return null;
328
        }
329 28
        return $this->$updatedAtAttribute;
330
    }
331
332
    /**
333
     * Get rules associated with `updatedAtAttribute`.
334
     * The default rule is safe. Because the [[TimestampBehavior]] will attach
335
     * the last update time automatically.
336
     * Under normal circumstances is not recommended to amend.
337
     * If `updatedAtAttribute` is not specified, the empty array will be given.
338
     * @return array rules
339
     */
340 353
    public function getUpdatedAtRules()
341
    {
342 353
        if (!is_string($this->updatedAtAttribute) || empty ($this->updatedAtAttribute)) {
343 22
            return [];
344
        }
345
        return [
346 346
            [[$this->updatedAtAttribute], 'safe'],
347
        ];
348
    }
349
350
    /**
351
     * Get expiration duration.
352
     * If `expiredAfterAttribute` is not specified, false will be given.
353
     * @return boolean
354
     */
355 219
    public function getExpiredAfter()
356
    {
357 219
        if (!is_string($this->expiredAfterAttribute) || empty($this->expiredAfterAttribute)) {
358 208
            return false;
359
        }
360 11
        return (int)($this->{$this->expiredAfterAttribute});
361
    }
362
363
    /**
364
     * Set expiration duration (in seconds).
365
     * If `expiredAfterAttribute` is not specified, this feature will be skipped,
366
     * and return false.
367
     * @param integer $expiredAfter the duration after which is expired (in seconds).
368
     * @return boolean|integer
369
     */
370 15
    public function setExpiredAfter($expiredAfter)
371
    {
372 15
        if (!is_string($this->expiredAfterAttribute) || empty($this->expiredAfterAttribute)) {
373 4
            return false;
374
        }
375 11
        return (int)($this->{$this->expiredAfterAttribute} = (int)$expiredAfter);
376
    }
377
378
    /**
379
     * Get rules associated with `expiredAfterAttribute`.
380
     * The default rule is unsigned integer.
381
     * Under normal circumstances is not recommended to amend.
382
     * If `expiredAfterAttribute` is not specified, the empty array will be given.
383
     * @return array The key of array is not specified.
384
     */
385 353
    public function getExpiredAfterRules()
386
    {
387 353
        if (!is_string($this->expiredAfterAttribute) || empty($this->expiredAfterAttribute)) {
388 330
            return [];
389
        }
390
        return [
391 23
            [[$this->expiredAfterAttribute], 'integer', 'min' => 0],
392
        ];
393
    }
394
395
    /**
396
     * Get enabled fields associated with timestamp, including `createdAtAttribute`,
397
     * `updatedAtAttribute` and `expiredAfterAttribute`.
398
     * @return array field list. The keys of array are not specified.
399
     */
400 138
    public function enabledTimestampFields()
401
    {
402 138
        $fields = [];
403 138
        if (is_string($this->createdAtAttribute) && !empty($this->createdAtAttribute)) {
404 138
            $fields[] = $this->createdAtAttribute;
405
        }
406 138
        if (is_string($this->updatedAtAttribute) && !empty($this->updatedAtAttribute)) {
407 125
            $fields[] = $this->updatedAtAttribute;
408
        }
409 138
        if (is_string($this->expiredAfterAttribute) && !empty($this->expiredAfterAttribute)) {
410 3
            $fields[] = $this->expiredAfterAttribute;
411
        }
412 138
        return $fields;
413
    }
414
415
    /**
416
     * Check it has been ever edited.
417
     * The judgement principle is to compare the creation time and the last update time,
418
     * if one of the two does not exist, then that has not been modified,
419
     * if both exist but not consistent, that modified.
420
     * You can override this method to implement more complex function.
421
     * @return boolean Whether this entity has ever been edited.
422
     */
423 11
    public function hasEverEdited()
424
    {
425 11
        if ($this->getCreatedAt() === null || $this->getUpdatedAt() === null) {
426
            return false;
427
        }
428 11
        return $this->getCreatedAt() !== $this->getUpdatedAt();
429
    }
430
}
431