TimestampTrait::offsetDatetime()   B
last analyzed

Complexity

Conditions 7
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 7

Importance

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