Event::getState()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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