• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sirn-se / websocket-php / 13834248469

13 Mar 2025 12:10PM UTC coverage: 99.624% (-0.4%) from 100.0%
13834248469

push

github

sirn-se
Improved documentation and stan

16 of 20 new or added lines in 3 files covered. (80.0%)

1059 of 1063 relevant lines covered (99.62%)

23.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.65
/src/Connection.php
1
<?php
2

3
/**
4
 * Copyright (C) 2014-2025 Textalk and contributors.
5
 * This file is part of Websocket PHP and is free software under the ISC License.
6
 */
7

8
namespace WebSocket;
9

10
use InvalidArgumentException;
11
use Phrity\Net\{
12
    Context,
13
    SocketStream,
14
};
15
use Psr\Http\Message\{
16
    MessageInterface,
17
    RequestInterface,
18
    ResponseInterface,
19
};
20
use Psr\Log\{
21
    LoggerAwareInterface,
22
    LoggerInterface,
23
    NullLogger
24
};
25
use Stringable;
26
use Throwable;
27
use WebSocket\Frame\FrameHandler;
28
use WebSocket\Http\HttpHandler;
29
use WebSocket\Exception\{
30
    ConnectionClosedException,
31
    ConnectionFailureException,
32
    ConnectionTimeoutException,
33
    Exception,
34
    ReconnectException,
35
};
36
use WebSocket\Message\{
37
    Message,
38
    MessageHandler
39
};
40
use WebSocket\Middleware\{
41
    MiddlewareHandler,
42
    MiddlewareInterface
43
};
44
use WebSocket\Trait\{
45
    SendMethodsTrait,
46
    StringableTrait
47
};
48

49
/**
50
 * WebSocket\Connection class.
51
 * A client/server connection, wrapping socket stream.
52
 */
53
class Connection implements LoggerAwareInterface, Stringable
54
{
55
    use SendMethodsTrait;
56
    use StringableTrait;
57

58
    private SocketStream $stream;
59
    private HttpHandler $httpHandler;
60
    private MessageHandler $messageHandler;
61
    private MiddlewareHandler $middlewareHandler;
62
    private LoggerInterface $logger;
63
    /** @var int<1, max> $frameSize */
64
    private int $frameSize = 4096;
65
    /** @var int<0, max> $timeout */
66
    private int $timeout = 60;
67
    private string $localName;
68
    private string $remoteName;
69
    private RequestInterface|null $handshakeRequest = null;
70
    private ResponseInterface|null $handshakeResponse = null;
71
    /** @var array<string, mixed> $meta */
72
    private array $meta = [];
73
    private bool $closed = false;
74

75

76
    /* ---------- Magic methods ------------------------------------------------------------------------------------ */
77

78
    public function __construct(SocketStream $stream, bool $pushMasked, bool $pullMaskedRequired, bool $ssl = false)
79
    {
80
        $this->stream = $stream;
103✔
81
        $this->httpHandler = new HttpHandler($this->stream, $ssl);
103✔
82
        $this->messageHandler = new MessageHandler(new FrameHandler($this->stream, $pushMasked, $pullMaskedRequired));
103✔
83
        $this->middlewareHandler = new MiddlewareHandler($this->messageHandler, $this->httpHandler);
103✔
84
        $this->setLogger(new NullLogger());
103✔
85
        $this->localName = $this->stream->getLocalName();
103✔
86
        $this->remoteName = $this->stream->getRemoteName();
103✔
87
    }
88

89
    public function __destruct()
90
    {
91
        if (!$this->closed && $this->isConnected()) {
103✔
92
            $this->stream->close();
72✔
93
        }
94
    }
95

96
    public function __toString(): string
97
    {
98
        return $this->stringable('%s:%s', $this->localName, $this->remoteName);
1✔
99
    }
100

101

102
    /* ---------- Configuration ------------------------------------------------------------------------------------ */
103

104
    /**
105
     * Set logger.
106
     * @param LoggerInterface $logger Logger implementation
107
     */
108
    public function setLogger(LoggerInterface $logger): void
109
    {
110
        $this->logger = $logger;
103✔
111
        $this->httpHandler->setLogger($logger);
103✔
112
        $this->messageHandler->setLogger($logger);
103✔
113
        $this->middlewareHandler->setLogger($logger);
103✔
114
        $this->logger->debug("[connection] Setting logger: " . get_class($logger));
103✔
115
    }
116

117
    /**
118
     * Set time out on connection.
119
     * @param int<0, max> $timeout Timeout part in seconds
120
     * @return self
121
     */
122
    public function setTimeout(int $timeout): self
123
    {
124
        if ($timeout < 0) {
67✔
NEW
125
            throw new InvalidArgumentException("Invalid timeout '{$timeout}' provided");
×
126
        }
127
        $this->timeout = $timeout;
67✔
128
        $this->stream->setTimeout($timeout, 0);
67✔
129
        $this->logger->debug("[connection] Setting timeout: {$timeout} seconds");
67✔
130
        return $this;
67✔
131
    }
132

133
    /**
134
     * Get timeout.
135
     * @return int Timeout in seconds.
136
     */
137
    public function getTimeout(): int
138
    {
139
        return $this->timeout;
1✔
140
    }
141

142
    /**
143
     * Set frame size.
144
     * @param int<1, max> $frameSize Frame size in bytes.
145
     * @return self
146
     */
147
    public function setFrameSize(int $frameSize): self
148
    {
149
        if ($frameSize < 1) {
67✔
NEW
150
            throw new InvalidArgumentException("Invalid frameSize '{$frameSize}' provided");
×
151
        }
152
        $this->frameSize = $frameSize;
67✔
153
        return $this;
67✔
154
    }
155

156
    /**
157
     * Get frame size.
158
     * @return int<1, max> Frame size in bytes
159
     */
160
    public function getFrameSize(): int
161
    {
162
        return max(1, $this->frameSize);
33✔
163
    }
164

165
    /**
166
     * Get current stream context.
167
     * @return Context
168
     */
169
    public function getContext(): Context
170
    {
171
        return $this->stream->getContext();
3✔
172
    }
173

174
    /**
175
     * Add a middleware.
176
     * @param MiddlewareInterface $middleware
177
     * @return self
178
     */
179
    public function addMiddleware(MiddlewareInterface $middleware): self
180
    {
181
        $this->middlewareHandler->add($middleware);
30✔
182
        $this->logger->debug("[connection] Added middleware: {$middleware}");
30✔
183
        return $this;
30✔
184
    }
185

186

187
    /* ---------- Connection management ---------------------------------------------------------------------------- */
188

189
    /**
190
     * If connected to stream.
191
     * @return bool
192
     */
193
    public function isConnected(): bool
194
    {
195
        return $this->stream->isConnected();
90✔
196
    }
197

198
    /**
199
     * If connection is readable.
200
     * @return bool
201
     */
202
    public function isReadable(): bool
203
    {
204
        return $this->stream->isReadable();
12✔
205
    }
206

207
    /**
208
     * If connection is writable.
209
     * @return bool
210
     */
211
    public function isWritable(): bool
212
    {
213
        return $this->stream->isWritable();
15✔
214
    }
215

216
    /**
217
     * Close connection stream.
218
     * @return self
219
     */
220
    public function disconnect(): self
221
    {
222
        $this->logger->info('[connection] Closing connection');
32✔
223
        $this->stream->close();
32✔
224
        $this->closed = true;
32✔
225
        return $this;
32✔
226
    }
227

228
    /**
229
     * Close connection stream reading.
230
     * @return self
231
     */
232
    public function closeRead(): self
233
    {
234
        $this->logger->info('[connection] Closing further reading');
4✔
235
        $this->stream->closeRead();
4✔
236
        return $this;
4✔
237
    }
238

239
    /**
240
     * Close connection stream writing.
241
     * @return self
242
     */
243
    public function closeWrite(): self
244
    {
245
        $this->logger->info('[connection] Closing further writing');
5✔
246
        $this->stream->closeWrite();
5✔
247
        return $this;
5✔
248
    }
249

250

251
    /* ---------- Connection state --------------------------------------------------------------------------------- */
252

253
    /**
254
     * Get name of local socket, or null if not connected.
255
     * @return string|null
256
     */
257
    public function getName(): string|null
258
    {
259
        return $this->localName;
3✔
260
    }
261

262
    /**
263
     * Get name of remote socket, or null if not connected.
264
     * @return string|null
265
     */
266
    public function getRemoteName(): string|null
267
    {
268
        return $this->remoteName;
2✔
269
    }
270

271
    /**
272
     * Set meta value on connection.
273
     * @param string $key Meta key
274
     * @param mixed $value Meta value
275
     */
276
    public function setMeta(string $key, mixed $value): void
277
    {
278
        $this->meta[$key] = $value;
8✔
279
    }
280

281
    /**
282
     * Get meta value on connection.
283
     * @param string $key Meta key
284
     * @return mixed Meta value
285
     */
286
    public function getMeta(string $key): mixed
287
    {
288
        return $this->meta[$key] ?? null;
8✔
289
    }
290

291
    /**
292
     * Tick operation on connection.
293
     */
294
    public function tick(): void
295
    {
296
        $this->middlewareHandler->processTick($this);
22✔
297
    }
298

299

300
    /* ---------- WebSocket Message methods ------------------------------------------------------------------------ */
301

302
    /**
303
     * Send message.
304
     * @template T of Message
305
     * @param T $message
306
     * @return T
307
     */
308
    public function send(Message $message): Message
309
    {
310
        return $this->pushMessage($message);
24✔
311
    }
312

313
    /**
314
     * Push a message to stream.
315
     * @template T of Message
316
     * @param T $message
317
     * @return T
318
     */
319
    public function pushMessage(Message $message): Message
320
    {
321
        try {
322
            return $this->middlewareHandler->processOutgoing($this, $message);
32✔
323
        } catch (Throwable $e) {
11✔
324
            $this->throwException($e);
11✔
325
        }
326
    }
327

328
    // Pull a message from stream
329
    public function pullMessage(): Message
330
    {
331
        try {
332
            return $this->middlewareHandler->processIncoming($this);
28✔
333
        } catch (Throwable $e) {
12✔
334
            $this->throwException($e);
12✔
335
        }
336
    }
337

338

339
    /* ---------- HTTP Message methods ----------------------------------------------------------------------------- */
340

341
    public function pushHttp(MessageInterface $message): MessageInterface
342
    {
343
        try {
344
            return $this->middlewareHandler->processHttpOutgoing($this, $message);
73✔
345
        } catch (Throwable $e) {
2✔
346
            $this->throwException($e);
2✔
347
        }
348
    }
349

350
    public function pullHttp(): MessageInterface
351
    {
352
        try {
353
            return $this->middlewareHandler->processHttpIncoming($this);
77✔
354
        } catch (Throwable $e) {
7✔
355
            $this->throwException($e);
7✔
356
        }
357
    }
358

359
    public function setHandshakeRequest(RequestInterface $request): self
360
    {
361
        $this->handshakeRequest = $request;
54✔
362
        return $this;
54✔
363
    }
364

365
    public function getHandshakeRequest(): RequestInterface|null
366
    {
367
        return $this->handshakeRequest;
54✔
368
    }
369

370
    public function setHandshakeResponse(ResponseInterface $response): self
371
    {
372
        $this->handshakeResponse = $response;
54✔
373
        return $this;
54✔
374
    }
375

376
    public function getHandshakeResponse(): ResponseInterface|null
377
    {
378
        return $this->handshakeResponse;
54✔
379
    }
380

381

382
    /* ---------- Internal helper methods -------------------------------------------------------------------------- */
383

384
    protected function throwException(Throwable $e): never
385
    {
386
        // Internal exceptions are handled and re-thrown
387
        if ($e instanceof ReconnectException) {
32✔
388
            $this->logger->info("[connection] {$e->getMessage()}", ['exception' => $e]);
2✔
389
            throw $e;
2✔
390
        }
391
        if ($e instanceof Exception) {
30✔
392
            $this->logger->error("[connection] {$e->getMessage()}", ['exception' => $e]);
20✔
393
            throw $e;
20✔
394
        }
395
        // External exceptions are converted to internal
396
        if ($this->isConnected()) {
10✔
397
            $meta = $this->stream->getMetadata();
9✔
398
            $json = json_encode($meta);
9✔
399
            if (!empty($meta['timed_out'])) {
9✔
400
                $this->logger->error("[connection] {$e->getMessage()}", ['exception' => $e, 'meta' => $meta]);
3✔
401
                throw new ConnectionTimeoutException();
3✔
402
            }
403
            if (!empty($meta['eof'])) {
6✔
404
                $this->logger->error("[connection] {$e->getMessage()}", ['exception' => $e, 'meta' => $meta]);
2✔
405
                throw new ConnectionClosedException();
2✔
406
            }
407
        }
408
        $this->logger->error("[connection] {$e->getMessage()}", ['exception' => $e]);
5✔
409
        throw new ConnectionFailureException();
5✔
410
    }
411
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc