Issues (5)

src/Server.php (4 issues)

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 9
    public function init()
67
    {
68 9
        if (!$this->eventCollection instanceof EventCollectionInterface) {
0 ignored issues
show
$this->eventCollection is always a sub-type of izumi\longpoll\EventCollectionInterface.
Loading history...
69 8
            $this->setEvents([]);
70
        }
71 9
    }
72
73
    /**
74
     * Prepares for sending the response.
75
     * @throws InvalidConfigException
76
     */
77 5
    protected function prepare()
78
    {
79 5
        $events = $this->eventCollection->getEvents();
80 5
        if (empty($events)) {
81 1
            throw new InvalidConfigException('At least one event should be added to the poll.');
82
        }
83 4
        foreach ($events as $eventKey => $event) {
84 4
            if (!isset($this->lastStates[$eventKey])) {
85 4
                $this->lastStates[$eventKey] = (int) Yii::$app->getRequest()->getQueryParam($event->getParamName());
86
            }
87
        }
88
89 4
        $this->version = '1.1';
90 4
        $this->getHeaders()
91 4
            ->set('Transfer-Encoding', 'chunked')
92 4
            ->set('Content-Type', 'application/json; charset=UTF-8');
93
94 4
        Yii::$app->getSession()->close();
95 4
    }
96
97
    /**
98
     * Sends the response content to the client.
99
     * @throws InvalidConfigException
100
     */
101 4
    protected function sendContent()
102
    {
103 4
        $events = $this->eventCollection->getEvents();
104 4
        $endTime = time() + $this->timeout;
105 4
        $connectionTestTime = time() + 1;
106 4
        if (!YII_ENV_TEST) {
107
            $this->clearOutputBuffers();
108
        }
109 4
        ignore_user_abort(true);
0 ignored issues
show
true of type true is incompatible with the type string expected by parameter $value of ignore_user_abort(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

109
        ignore_user_abort(/** @scrutinizer ignore-type */ true);
Loading history...
110
        do {
111 4
            $triggered = [];
112 4
            foreach ($events as $eventKey => $event) {
113 4
                $event->updateState();
114 4
                if ($event->getState() !== $this->lastStates[$eventKey]) {
115 4
                    $triggered[$eventKey] = $event;
116
                }
117
            }
118 4
            if (!empty($triggered)) {
119 3
                break;
120
            }
121 4
            usleep($this->sleepTime);
122 4
            if (time() >= $connectionTestTime) {
123 4
                echo '0';
124 4
                flush();
125 4
                if (connection_aborted()) {
126
                    Yii::debug('Client disconnected', __METHOD__);
127
                    Yii::$app->end();
128
                }
129 4
                $connectionTestTime++;
130
            }
131 4
        } while (time() < $endTime);
132
133 4
        $this->_triggeredEvents = $triggered;
134
135 4
        if (!empty($triggered) && is_callable($this->callback)) {
136 3
            call_user_func($this->callback, $this);
137
        }
138
139 4
        $params = (array) $this->responseParams;
140 4
        $json = Json::encode([
141 4
            'data' => $this->responseData,
142 4
            'params' => LongPoll::createPollParams($this->eventCollection, $params)
143
        ]);
144
145 4
        echo dechex(strlen($json)), "\r\n", $json, "\r\n";
146 4
        echo "0\r\n\r\n";
147 4
    }
148
149
    /**
150
     * @param EventInterface|string $event
151
     * @param int|null $lastState
152
     */
153 6
    public function addEvent($event, $lastState = null)
154
    {
155 6
        $event = $this->eventCollection->addEvent($event);
156 6
        if ($lastState !== null) {
157 6
            if (!is_int($lastState)) {
0 ignored issues
show
The condition is_int($lastState) is always true.
Loading history...
158 1
                throw new InvalidArgumentException('$lastState must be an integer');
159
            }
160 5
            $this->lastStates[$event->getKey()] = $lastState;
161
        }
162 5
    }
163
164
    /**
165
     * @param array|EventInterface[]|string $events the events for waiting (any).
166
     * @throws InvalidConfigException
167
     */
168 9
    public function setEvents($events)
169
    {
170 9
        if (!$this->eventCollection instanceof EventCollectionInterface) {
0 ignored issues
show
$this->eventCollection is always a sub-type of izumi\longpoll\EventCollectionInterface.
Loading history...
171 9
            $this->eventCollection = Yii::createObject($this->eventCollectionClass);
172
        }
173 9
        $this->eventCollection->setEvents($events);
174 9
    }
175
176
    /**
177
     * @return EventInterface[]|null triggered events during poll run (key => event)
178
     */
179 3
    public function getTriggeredEvents()
180
    {
181 3
        return $this->_triggeredEvents;
182
    }
183
184
}
185