Completed
Push — master ( 5adbcd...41097b )
by Viktor
03:52 queued 43s
created

Event::updateState()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 2
rs 9.4285
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 41
    public function init()
55
    {
56 41
        parent::init();
57 41
        if ($this->_key === null) {
58 1
            throw new InvalidConfigException("The event key is required.");
59
        }
60 40
        if (ctype_alnum($this->_key) && StringHelper::byteLength($this->_key) <= 32) {
61 33
            $this->_keyNormalized = $this->_key;
62
        } else {
63 10
            $this->_keyNormalized = md5($this->_key);
64
        }
65 40
        $this->statesPath = Yii::getAlias($this->statesPath);
66 40
        if (!is_dir($this->statesPath)) {
67 2
            FileHelper::createDirectory($this->statesPath);
68
        }
69 40
        $this->_filePath = $this->statesPath . DIRECTORY_SEPARATOR . $this->_keyNormalized;
70 40
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75 45
    public function setKey($key)
76
    {
77 45
        if ($this->_key !== null) {
78 1
            throw new InvalidCallException("The key can't be changed.");
79
        }
80 45
        if (!is_string($key)) {
81 5
            throw new InvalidParamException("The event key must be a string.");
82
        }
83 40
        $this->_key = $key;
84 40
    }
85
86
    /**
87
     * @inheritdoc
88
     */
89 27
    public function getKey()
90
    {
91 27
        return $this->_key;
92
    }
93
94
    /**
95
     * @inheritdoc
96
     */
97 6
    public function trigger()
98
    {
99 6
        $filePath = $this->_filePath;
100 6
        $tries = 4;
101 6
        $lastError = null;
102 6
        $state = null;
103 6
        set_error_handler(function () use (&$lastError) {
104 1
            $lastError = func_get_arg(1);
105 6
        });
106 6
        while ($tries > 0) {
107 6
            $tries--;
108
109 6
            $file = fopen($filePath, 'c+');
110
111 6
            if ($file === false) {
112 1
                break;
113
            }
114
115 5
            if (!flock($file, LOCK_EX | LOCK_NB)) {
116 1
                fclose($file);
117 1
                usleep(250000);
118 1
                continue;
119
            }
120
121 5
            $state = (int) stream_get_contents($file) ?: 0;
122 5
            if ($state >= time() + 1000000) {
123 1
                $state = 0;
124
            }
125 5
            $state++;
126
127 5
            ftruncate($file, 0);
128 5
            rewind($file);
129 5
            fwrite($file, (string) $state);
130 5
            flock($file, LOCK_UN);
131 5
            fclose($file);
132
133 5
            if (touch($filePath, $state)) {
134 5
                $this->_state = $state;
135 5
                $lastError = null;
136 5
                break;
137
            }
138
        }
139 6
        restore_error_handler();
140
141 6
        if ($lastError === null) {
142 5
            return $state;
143
        }
144
145 1
        Yii::warning("Unable to trigger event '{$this->_key}': {$lastError}", __METHOD__);
146
147 1
        return null;
148
    }
149
150
    /**
151
     * Trigger an event given the event key.
152
     * @param string $key event key
153
     * @return int|null new state or null on failure
154
     */
155 3
    public static function triggerByKey($key)
156
    {
157 3
        return (new static(['key' => $key]))->trigger();
158
    }
159
160
    /**
161
     * @inheritdoc
162
     */
163 13
    public function updateState()
164
    {
165 13
        clearstatcache(true, $this->_filePath);
166
167 13
        $this->_state = @filemtime($this->_filePath) ?: 0;
168 13
    }
169
170
    /**
171
     * @inheritdoc
172
     */
173 13
    public function getState()
174
    {
175 13
        if ($this->_state === null) {
176 12
            $this->updateState();
177
        }
178
179 13
        return $this->_state;
180
    }
181
182
    /**
183
     * @inheritdoc
184
     */
185 14
    public function getParamName()
186
    {
187 14
        return $this->eventParamPrefix . $this->_keyNormalized;
188
    }
189
}
190