Passed
Pull Request — master (#6)
by
unknown
03:33
created

Server::prepare()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 11
c 0
b 0
f 0
nc 4
nop 0
dl 0
loc 18
ccs 0
cts 12
cp 0
crap 20
rs 9.9
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 izumi\longpoll\widgets\LongPoll;
11
use Yii;
12
use yii\base\InvalidArgumentException;
13
use yii\base\InvalidConfigException;
14
use yii\helpers\Json;
15
use yii\web\Response;
16
17
/**
18
 * Class implements long polling connection.
19
 *
20
 * @property EventInterface[]|null $triggeredEvents
21
 * @author Viktor Khokhryakov <[email protected]>
22
 */
23
class Server extends Response
24
{
25
    /**
26
     * @var callable
27
     */
28
    public $callback;
29
    /**
30
     * @var mixed response data
31
     */
32
    public $responseData;
33
    /**
34
     * @var array query params
35
     */
36
    public $responseParams = [];
37
    /**
38
     * @var int how long poll will be (in seconds).
39
     */
40
    public $timeout = 25;
41
    /**
42
     * @var int time between events check (in microseconds).
43
     */
44
    public $sleepTime = 250000;
45
    /**
46
     * @var EventCollectionInterface events for waiting (any).
47
     */
48
    public $eventCollection;
49
    /**
50
     * @var string event collection class name.
51
     */
52
    public $eventCollectionClass = EventCollection::class;
53
    /**
54
     * @var array events (string eventId => int lastState) for waiting (any).
55
     */
56
    protected $lastStates = [];
57
    /**
58
     * @var EventInterface[]|null
59
     */
60
    protected $_triggeredEvents;
61
62
    /**
63
     * @inheritdoc
64
     * @throws InvalidConfigException
65
     */
66 1
    public function init()
67
    {
68 1
        if (!$this->eventCollection instanceof EventCollectionInterface) {
0 ignored issues
show
introduced by
$this->eventCollection is always a sub-type of izumi\longpoll\EventCollectionInterface.
Loading history...
69
            $this->setEvents([]);
70
        }
71 1
    }
72
73
    /**
74
     * Prepares for sending the response.
75
     * @throws InvalidConfigException
76
     */
77
    protected function prepare()
78
    {
79
        $events = $this->eventCollection->getEvents();
80
        if (empty($events)) {
81
            throw new InvalidConfigException('At least one event should be added to the poll.');
82
        }
83
        foreach ($events as $eventKey => $event) {
84
            if (!isset($this->lastStates[$eventKey])) {
85
                $this->lastStates[$eventKey] = (int) Yii::$app->getRequest()->getQueryParam($event->getParamName());
86
            }
87
        }
88
89
        $this->version = '1.1';
90
        $this->getHeaders()
91
            ->set('Transfer-Encoding', 'chunked')
92
            ->set('Content-Type', 'application/json; charset=UTF-8');
93
94
        Yii::$app->getSession()->close();
95
    }
96
97
    /**
98
     * Sends the response content to the client.
99
     * @throws InvalidConfigException
100
     */
101
    protected function sendContent()
102
    {
103
        $events = $this->eventCollection->getEvents();
104
        $endTime = time() + $this->timeout;
105
        $connectionTestTime = time() + 1;
106
        if (!YII_ENV_TEST) {
107
            $this->clearOutputBuffers();
108
        }
109
        ignore_user_abort(true);
110
        do {
111
            $triggered = [];
112
            foreach ($events as $eventKey => $event) {
113
                $event->updateState();
114
                if ($event->getState() !== $this->lastStates[$eventKey]) {
115
                    $triggered[$eventKey] = $event;
116
                }
117
            }
118
            if (!empty($triggered)) {
119
                break;
120
            }
121
            usleep($this->sleepTime);
122
            if (time() >= $connectionTestTime) {
123
                echo '0';
124
                flush();
125
                if (connection_aborted()) {
126
                    Yii::debug('Client disconnected', __METHOD__);
127
                    Yii::$app->end();
128
                }
129
                $connectionTestTime++;
130
            }
131
        } while (time() < $endTime);
132
133
        $this->_triggeredEvents = $triggered;
134
135
        if (!empty($triggered) && is_callable($this->callback)) {
136
            call_user_func($this->callback, $this);
137
        }
138
139
        $params = (array) $this->responseParams;
140
        $json = Json::encode([
141
            'data' => $this->responseData,
142
            'params' => LongPoll::createPollParams($this->eventCollection, $params)
143
        ]);
144
145
        //echo dechex(strlen($json)), "\r\n", $json, "\r\n";
146
        //echo "0\r\n\r\n";
147
        
148
        echo $json;
149
    }
150
151
    /**
152
     * @param EventInterface|string $event
153
     * @param int|null $lastState
154
     */
155
    public function addEvent($event, $lastState = null)
156
    {
157
        $event = $this->eventCollection->addEvent($event);
158
        if ($lastState !== null) {
159
            if (!is_int($lastState)) {
0 ignored issues
show
introduced by
The condition is_int($lastState) is always true.
Loading history...
160
                throw new InvalidArgumentException('$lastState must be an integer');
161
            }
162
            $this->lastStates[$event->getKey()] = $lastState;
163
        }
164
    }
165
166
    /**
167
     * @param array|EventInterface[]|string $events the events for waiting (any).
168
     * @throws InvalidConfigException
169
     */
170 1
    public function setEvents($events)
171
    {
172 1
        if (!$this->eventCollection instanceof EventCollectionInterface) {
0 ignored issues
show
introduced by
$this->eventCollection is always a sub-type of izumi\longpoll\EventCollectionInterface.
Loading history...
173 1
            $this->eventCollection = Yii::createObject($this->eventCollectionClass);
174
        }
175 1
        $this->eventCollection->setEvents($events);
176 1
    }
177
178
    /**
179
     * @return EventInterface[]|null triggered events during poll run (key => event)
180
     */
181
    public function getTriggeredEvents()
182
    {
183
        return $this->_triggeredEvents;
184
    }
185
186
}
187