Completed
Push — master ( e2e764...278c93 )
by Viktor
01:45
created

Event   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Test Coverage

Coverage 98.31%

Importance

Changes 0
Metric Value
dl 0
loc 154
ccs 58
cts 59
cp 0.9831
rs 10
c 0
b 0
f 0
wmc 22

8 Methods

Rating   Name   Duplication   Size   Complexity  
B init() 0 16 5
A setKey() 0 9 3
A getKey() 0 3 1
A updateState() 0 5 2
A getState() 0 7 2
A triggerByKey() 0 3 1
A getParamName() 0 3 1
C trigger() 0 41 7
1
<?php
2
/**
3
 * @link https://github.com/Izumi-kun/yii2-longpoll
4
 * @copyright Copyright (c) 2017 Viktor Khokhryakov
5
 * @license http://opensource.org/licenses/BSD-3-Clause
6
 */
7
8
namespace izumi\longpoll;
9
10
use Yii;
11
use yii\base\BaseObject;
12
use yii\base\InvalidCallException;
13
use yii\base\InvalidConfigException;
14
use yii\base\InvalidParamException;
15
use yii\helpers\FileHelper;
16
use yii\helpers\StringHelper;
17
18
/**
19
 * Event uses filesystem for monitoring and triggering events.
20
 * @property string $key The event key.
21
 * @property int $state The event state.
22
 * @author Viktor Khokhryakov <[email protected]>
23
 */
24
class Event extends BaseObject implements EventInterface
25
{
26
    /**
27
     * @var string prefix of the parameter storing the state of event
28
     */
29
    protected $eventParamPrefix = 'event-';
30
    /**
31
     * @var string the directory to store state files. You may use path alias here.
32
     */
33
    protected $statesPath = '@runtime/events';
34
    /**
35
     * @var string event key
36
     */
37
    private $_key;
38
    /**
39
     * @var string normalized event key
40
     */
41
    private $_keyNormalized;
42
    /**
43
     * @var string the state file path
44
     */
45
    private $_filePath;
46
    /**
47
     * @var int current state
48
     */
49
    private $_state;
50
51
    /**
52
     * @inheritdoc
53
     */
54 33
    public function init()
55
    {
56 33
        parent::init();
57 33
        if ($this->_key === null) {
58 1
            throw new InvalidConfigException("The event key is required.");
59
        }
60 32
        if (ctype_alnum($this->_key) && StringHelper::byteLength($this->_key) <= 32) {
61 25
            $this->_keyNormalized = $this->_key;
62
        } else {
63 10
            $this->_keyNormalized = md5($this->_key);
64
        }
65 32
        $this->statesPath = Yii::getAlias($this->statesPath);
66 32
        if (!is_dir($this->statesPath)) {
67 1
            FileHelper::createDirectory($this->statesPath);
68
        }
69 32
        $this->_filePath = $this->statesPath . DIRECTORY_SEPARATOR . $this->_keyNormalized;
70 32
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75 37
    public function setKey($key)
76
    {
77 37
        if ($this->_key !== null) {
78 1
            throw new InvalidCallException("The key can't be changed.");
79
        }
80 37
        if (!is_string($key)) {
81 5
            throw new InvalidParamException("The event key must be a string.");
82
        }
83 32
        $this->_key = $key;
84 32
    }
85
86
    /**
87
     * @inheritdoc
88
     */
89 20
    public function getKey()
90
    {
91 20
        return $this->_key;
92
    }
93
94
    /**
95
     * @inheritdoc
96
     */
97 5
    public function trigger()
98
    {
99 5
        $filePath = $this->_filePath;
100 5
        $tries = 4;
101
102 5
        while ($tries > 0) {
103 5
            $tries--;
104
105 5
            $file = fopen($filePath, 'c+');
106
107 5
            if ($file === false) {
108
                break;
109
            }
110
111 5
            if (!flock($file, LOCK_EX | LOCK_NB)) {
112 1
                fclose($file);
113 1
                usleep(250000);
114 1
                continue;
115
            }
116
117 5
            $state = (int) stream_get_contents($file) ?: 0;
118 5
            if ($state >= time() + 1000000) {
119 1
                $state = 0;
120
            }
121 5
            $state++;
122
123 5
            ftruncate($file, 0);
124 5
            rewind($file);
125 5
            fwrite($file, (string) $state);
126 5
            flock($file, LOCK_UN);
127 5
            fclose($file);
128
129 5
            if (touch($filePath, $state)) {
130 5
                $this->_state = $state;
131 5
                return $state;
132
            }
133
        }
134
135 1
        Yii::warning("Unable to trigger event '{$this->_key}'", __METHOD__);
136
137 1
        return null;
138
    }
139
140
    /**
141
     * Trigger an event given the event key.
142
     * @param string $key event key
143
     * @return int|null new state or null on failure
144
     */
145 3
    public static function triggerByKey($key)
146
    {
147 3
        return (new static(['key' => $key]))->trigger();
148
    }
149
150
    /**
151
     * @inheritdoc
152
     */
153 10
    public function updateState()
154
    {
155 10
        clearstatcache(true, $this->_filePath);
156
157 10
        $this->_state = @filemtime($this->_filePath) ?: 0;
158 10
    }
159
160
    /**
161
     * @inheritdoc
162
     */
163 10
    public function getState()
164
    {
165 10
        if ($this->_state === null) {
166 9
            $this->updateState();
167
        }
168
169 10
        return $this->_state;
170
    }
171
172
    /**
173
     * @inheritdoc
174
     */
175 11
    public function getParamName()
176
    {
177 11
        return $this->eventParamPrefix . $this->_keyNormalized;
178
    }
179
}
180