Completed
Push — master ( 15c8d0...bf148f )
by vistart
04:55
created

TimestampTrait::getExpiredAfterRules()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 4
cts 4
cp 1
rs 9.6666
c 0
b 0
f 0
cc 3
eloc 5
nc 2
nop 0
crap 3
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
    /**
60
     * @var Closure
61
     */
62
    public $expiredRemovingCallback;
63
    public static $eventExpiredRemoved = 'expiredRemoved';
64
    
65
    /**
66
     * Check this entity whether expired.
67
     * This feature require creation time. If creation time didn't record, false
68
     * is returned.
69
     * This feature also need expiration duration. If expiration duration didn't
70
     * record, false is returned.
71
     * @return boolean
72
     */
73 95
    public function getIsExpired()
74
    {
75 95
        $createdAt = $this->getCreatedAt();
76 95
        if ($this->getExpiredAfter() === false || $createdAt === null) {
77 87
            return false;
78
        }
79 11
        return $this->offsetDatetime($this->currentDatetime(), -$this->getExpiredAfter()) > $createdAt;
80
    }
81
    
82
    /**
83
     * Remove myself if expired.
84
     * The `expiredRemovingCallback` will be called before removing itself,
85
     * then it would trigger `static::$eventExpiredRemoved` event, and attach
86
     * the removing results.
87
     * @return boolean
88
     */
89 92
    public function removeIfExpired()
90
    {
91 92
        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...
92 11
            if (($this->expiredRemovingCallback instanceof Closure || is_array($this->expiredRemovingCallback)) && is_callable($this->expiredRemovingCallback)) {
93 3
                $result = call_user_func($this->expiredRemovingCallback, $this);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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