Passed
Push — master ( 4c5f08...24f884 )
by Dawid
02:28
created

Server::createClient()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php declare(strict_types=1);
2
3
namespace Igni\Network;
4
5
use Igni\Exception\RuntimeException;
6
use Igni\Network\Exception\ClientException;
7
use Igni\Network\Exception\ServerException;
8
use Igni\Network\Server\Client;
9
use Igni\Network\Server\Configuration;
10
use Igni\Network\Server\HandlerFactory;
11
use Igni\Network\Server\Listener;
12
use Igni\Network\Server\LogWriter;
13
use Igni\Network\Server\OnCloseListener;
14
use Igni\Network\Server\OnConnectListener;
15
use Igni\Network\Server\OnReceiveListener;
16
use Igni\Network\Server\OnShutdownListener;
17
use Igni\Network\Server\OnStartListener;
18
use Igni\Network\Server\ServerStats;
19
use Psr\Log\LoggerInterface;
20
use SplQueue;
21
use Swoole\Server as SwooleServer;
22
23
use function extension_loaded;
24
25
/**
26
 * Http server implementation based on swoole extension.
27
 *
28
 * @package Igni\Http
29
 */
30
class Server implements HandlerFactory
31
{
32
    private const SWOOLE_EXT_NAME = 'swoole';
33
34
    /**
35
     * @var SwooleServer|null
36
     */
37
    protected $handler;
38
39
    /**
40
     * @var Configuration
41
     */
42
    protected $configuration;
43
44
    /**
45
     * @var SplQueue[]
46
     */
47
    protected $listeners = [];
48
49
    /**
50
     * @var LoggerInterface
51
     */
52
    protected $logger;
53
54
    /**
55
     * @var HandlerFactory
56
     */
57
    protected $handlerFactory;
58
59
    /**
60
     * @var Client[]
61
     */
62
    private $clients = [];
63
64
    /**
65
     * @var bool
66
     */
67
    private $running = false;
68
69 15
    public function __construct(
70
        Configuration $settings = null,
71
        LoggerInterface $logger = null,
72
        HandlerFactory $handlerFactory = null
73
    ) {
74 15
        if (!extension_loaded(self::SWOOLE_EXT_NAME)) {
75
            throw new RuntimeException('Swoole extenstion is missing, please install it and try again.');
76
        }
77
78 15
        $this->handlerFactory = $handlerFactory ?? $this;
79 15
        $this->configuration = $settings ?? new Configuration();
80 15
        $this->logger = new LogWriter($logger);
81 15
    }
82
83
    /**
84
     * Return server configuration.
85
     *
86
     * @return Configuration
87
     */
88 2
    public function getConfiguration(): Configuration
89
    {
90 2
        return $this->configuration;
91
    }
92
93
    /**
94
     * @param int $id
95
     * @return Client
96
     * @throws ClientException if client was not found
97
     */
98 6
    public function getClient(int $id): Client
99
    {
100 6
        if (!isset($this->clients[$id])) {
101 1
            throw ClientException::forInactiveClient($id);
102
        }
103 6
        return $this->clients[$id];
104
    }
105
106
    /**
107
     * Adds listener that is attached to server once it is run.
108
     *
109
     * @param Listener $listener
110
     */
111 11
    public function addListener(Listener $listener): void
112
    {
113 11
        $this->addListenerByType($listener, OnStartListener::class);
114 11
        $this->addListenerByType($listener, OnCloseListener::class);
115 11
        $this->addListenerByType($listener, OnConnectListener::class);
116 11
        $this->addListenerByType($listener, OnShutdownListener::class);
117 11
        $this->addListenerByType($listener, OnReceiveListener::class);
118 11
    }
119
120 11
    protected function addListenerByType(Listener $listener, string $type): void
121
    {
122 11
        if ($listener instanceof $type) {
123 11
            if (!isset($this->listeners[$type])) {
124 11
                $this->listeners[$type] = new SplQueue();
125
            }
126 11
            $this->listeners[$type]->push($listener);
127
        }
128 11
    }
129
130
    /**
131
     * Checks if listener exists.
132
     *
133
     * @param Listener $listener
134
     * @return bool
135
     */
136 1
    public function hasListener(Listener $listener): bool
137
    {
138
        /** @var SplQueue $listenerCollection */
139 1
        foreach ($this->listeners as $listenerCollection) {
140 1
            foreach ($listenerCollection as $current) {
141 1
                if ($current === $listener) {
142 1
                    return true;
143
                }
144
            }
145
        }
146
147 1
        return false;
148
    }
149
150
    /**
151
     * Returns information about server.
152
     *
153
     * @return ServerStats
154
     */
155 1
    public function getServerStats(): ServerStats
156
    {
157 1
        if (!$this->running) {
158 1
            throw ServerException::forMethodCallOnIdleServer(__METHOD__);
159
        }
160
        return new ServerStats($this->handler->stats());
161
    }
162
163
    public function createHandler(Configuration $configuration)
164
    {
165
        $flags = SWOOLE_TCP;
166
        if ($configuration->isSslEnabled()) {
167
            $flags |= SWOOLE_SSL;
168
        }
169
        $settings = $configuration->toArray();
170
        $handler = new SwooleServer($settings['address'], $settings['port'], SWOOLE_PROCESS, $flags);
171
        $handler->set($settings);
172
173
        return $handler;
174
    }
175
176 10
    public function start(): void
177
    {
178 10
        $this->addListener($this->logger);
179 10
        $this->handler = $this->handlerFactory->createHandler($this->configuration);
180 10
        $this->createListeners();
181 10
        $this->handler->start();
182 10
        $this->running = true;
183 10
    }
184
185 1
    public function stop(): void
186
    {
187 1
        if ($this->handler !== null) {
188 1
            $this->handler->shutdown();
189 1
            $this->handler = null;
190
        }
191 1
        $this->running = false;
192 1
    }
193
194 1
    public function isRunning(): bool
195
    {
196 1
        return $this->running;
197
    }
198
199 10
    protected function createListeners(): void
200
    {
201 10
        $this->createOnConnectListener();
202 10
        $this->createOnCloseListener();
203 10
        $this->createOnShutdownListener();
204 10
        $this->createOnStartListener();
205 10
        $this->createOnReceiveListener();
206 10
    }
207
208 6
    private function createClient($handler, int $clientId): Client
209
    {
210 6
        return $this->clients[$clientId] = new Client($handler, $clientId);
211
    }
212
213 4
    private function destroyClient(int $clientId): void
214
    {
215 4
        unset($this->clients[$clientId]);
216 4
    }
217
218 10
    protected function createOnConnectListener(): void
219
    {
220
        $this->handler->on('Connect', function($handler, int $clientId) {
221 6
            $this->createClient($handler, $clientId);
222
223 6
            if (!isset($this->listeners[OnConnectListener::class])) {
224
                return;
225
            }
226
227 6
            $queue = clone $this->listeners[OnConnectListener::class];
228
            /** @var OnConnectListener $listener */
229 6
            while (!$queue->isEmpty() && $listener = $queue->pop()) {
230 6
                $listener->onConnect($this, $this->getClient($clientId));
231
            }
232 10
        });
233 10
    }
234
235 10
    protected function createOnCloseListener(): void
236
    {
237
        $this->handler->on('Close', function($handler, int $clientId) {
238 4
            if (isset($this->listeners[OnCloseListener::class])) {
239
240 4
                $queue = clone $this->listeners[OnCloseListener::class];
241
                /** @var OnCloseListener $listener */
242 4
                while (!$queue->isEmpty() && $listener = $queue->pop()) {
243 4
                    $listener->onClose($this, $this->getClient($clientId));
244
                }
245
            }
246
247 4
            $this->destroyClient($clientId);
248 10
        });
249 10
    }
250
251 10
    protected function createOnShutdownListener(): void
252
    {
253
        $this->handler->on('Shutdown', function() {
254 1
            if (!isset($this->listeners[OnShutdownListener::class])) {
255
                return;
256
            }
257
258 1
            $queue = clone $this->listeners[OnShutdownListener::class];
259
260
            /** @var OnShutdownListener $listener */
261 1
            while (!$queue->isEmpty() && $listener = $queue->pop()) {
262 1
                $listener->onShutdown($this);
263
            }
264 1
            $this->clients = [];
265 10
        });
266 10
    }
267
268 10
    protected function createOnStartListener(): void
269
    {
270
        $this->handler->on('Start', function() {
271 1
            if (!isset($this->listeners[OnStartListener::class])) {
272
                return;
273
            }
274
275 1
            $queue = clone $this->listeners[OnStartListener::class];
276
            /** @var OnStartListener $listener */
277 1
            while (!$queue->isEmpty() && $listener = $queue->pop()) {
278 1
                $listener->onStart($this);
279
            }
280 10
        });
281 10
    }
282
283 10
    protected function createOnReceiveListener(): void
284
    {
285
        $this->handler->on('Receive', function ($handler, int $clientId, int $fromId, string $data) {
286 1
            if (!isset($this->listeners[OnReceiveListener::class])) {
287
                return;
288
            }
289
290 1
            $queue = clone $this->listeners[OnReceiveListener::class];
291
292
            /** @var OnReceiveListener $listener */
293 1
            while (!$queue->isEmpty() && $listener = $queue->pop()) {
294 1
                $listener->onReceive($this, $this->getClient($clientId), $data);
295
            }
296 10
        });
297 10
    }
298
}
299