Skip to content

Commit 556d9b6

Browse files
authored
Merge pull request #61 from clue-labs/php8
Support PHP 8, use Socket object instead of socket resource
2 parents 7b32284 + fadd298 commit 556d9b6

7 files changed

Lines changed: 71 additions & 14 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ jobs:
1212
os:
1313
- ubuntu-latest
1414
- windows-latest
15-
php:
15+
php:
16+
- 8.0
1617
- 7.4
1718
- 7.3
1819
- 7.2

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
204204

205205
This project aims to run on any platform and thus does not require any PHP
206206
extensions besides `ext-sockets` and supports running on legacy PHP 5.3 through
207-
current PHP 7+.
207+
current PHP 8+.
208208
It's *highly recommended to use PHP 7+* for this project.
209209

210210
## Tests

src/Exception.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Exception extends RuntimeException
99
/**
1010
* Create an Exception after a socket operation on the given $resource failed
1111
*
12-
* @param resource $resource
12+
* @param \Socket|resource $resource
1313
* @param string $messagePrefix
1414
* @return self
1515
* @uses socket_last_error() to get last socket error code
@@ -18,7 +18,13 @@ class Exception extends RuntimeException
1818
*/
1919
public static function createFromSocketResource($resource, $messagePrefix = 'Socket operation failed')
2020
{
21-
if (is_resource($resource)) {
21+
if (PHP_VERSION_ID >= 80000) {
22+
try {
23+
$code = socket_last_error($resource);
24+
} catch (\Error $e) {
25+
$code = SOCKET_ENOTSOCK;
26+
}
27+
} elseif (is_resource($resource)) {
2228
$code = socket_last_error($resource);
2329
socket_clear_error($resource);
2430
} else {

src/Factory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ public function createIcmp6()
170170
* @param int $protocol
171171
* @return \Socket\Raw\Socket
172172
* @throws Exception if creating socket fails
173+
* @throws \Error PHP 8 only: throws \Error when arguments are invalid
173174
* @uses socket_create()
174175
*/
175176
public function create($domain, $type, $protocol)
@@ -189,6 +190,7 @@ public function create($domain, $type, $protocol)
189190
* @param int $protocol
190191
* @return \Socket\Raw\Socket[]
191192
* @throws Exception if creating pair of sockets fails
193+
* @throws \Error PHP 8 only: throws \Error when arguments are invalid
192194
* @uses socket_create_pair()
193195
*/
194196
public function createPair($domain, $type, $protocol)
@@ -207,6 +209,7 @@ public function createPair($domain, $type, $protocol)
207209
* @param int $backlog
208210
* @return \Socket\Raw\Socket
209211
* @throws Exception if creating listening socket fails
212+
* @throws \Error PHP 8 only: throws \Error when arguments are invalid
210213
* @uses socket_create_listen()
211214
* @see self::createServer() as an alternative to bind to specific IP, IPv6, UDP, UNIX, UGP
212215
*/

src/Socket.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Socket
1313
/**
1414
* reference to actual socket resource
1515
*
16-
* @var resource
16+
* @var \Socket|resource
1717
*/
1818
private $resource;
1919

@@ -22,7 +22,7 @@ class Socket
2222
*
2323
* should usually not be called manually, see Factory
2424
*
25-
* @param resource $resource
25+
* @param \Socket|resource $resource
2626
* @see Factory as the preferred (and simplest) way to construct socket instances
2727
*/
2828
public function __construct($resource)
@@ -33,7 +33,7 @@ public function __construct($resource)
3333
/**
3434
* get actual socket resource
3535
*
36-
* @return resource
36+
* @return \Socket|resource returns the socket resource (a `Socket` object as of PHP 8)
3737
*/
3838
public function getResource()
3939
{
@@ -45,6 +45,7 @@ public function getResource()
4545
*
4646
* @return \Socket\Raw\Socket new connected socket used for communication
4747
* @throws Exception on error, if this is not a listening socket or there's no connection pending
48+
* @throws \Error PHP 8 only: throws \Error when socket is invalid
4849
* @see self::selectRead() to check if this listening socket can accept()
4950
* @see Factory::createServer() to create a listening socket
5051
* @see self::listen() has to be called first
@@ -67,6 +68,7 @@ public function accept()
6768
* @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
6869
* @return self $this (chainable)
6970
* @throws Exception on error
71+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
7072
* @uses socket_bind()
7173
*/
7274
public function bind($address)
@@ -85,6 +87,7 @@ public function bind($address)
8587
* its socket resource remains closed and most further operations will fail!
8688
*
8789
* @return self $this (chainable)
90+
* @throws \Error PHP 8 only: throws \Error when socket is invalid
8891
* @see self::shutdown() should be called before closing socket
8992
* @uses socket_close()
9093
*/
@@ -100,6 +103,7 @@ public function close()
100103
* @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
101104
* @return self $this (chainable)
102105
* @throws Exception on error
106+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
103107
* @uses socket_connect()
104108
*/
105109
public function connect($address)
@@ -126,6 +130,7 @@ public function connect($address)
126130
* @param float $timeout maximum time to wait (in seconds)
127131
* @return self $this (chainable)
128132
* @throws Exception on error
133+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
129134
* @uses self::setBlocking() to enable non-blocking mode
130135
* @uses self::connect() to initiate the connection
131136
* @uses self::selectWrite() to wait for the connection to complete
@@ -166,6 +171,7 @@ public function connectTimeout($address, $timeout)
166171
* @param int $optname
167172
* @return mixed
168173
* @throws Exception on error
174+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
169175
* @uses socket_get_option()
170176
*/
171177
public function getOption($level, $optname)
@@ -182,6 +188,7 @@ public function getOption($level, $optname)
182188
*
183189
* @return string
184190
* @throws Exception on error
191+
* @throws \Error PHP 8 only: throws \Error when socket is invalid
185192
* @uses socket_getpeername()
186193
*/
187194
public function getPeerName()
@@ -198,6 +205,7 @@ public function getPeerName()
198205
*
199206
* @return string
200207
* @throws Exception on error
208+
* @throws \Error PHP 8 only: throws \Error when socket is invalid
201209
* @uses socket_getsockname()
202210
*/
203211
public function getSockName()
@@ -215,6 +223,7 @@ public function getSockName()
215223
* @param int $backlog maximum number of incoming connections to be queued
216224
* @return self $this (chainable)
217225
* @throws Exception on error
226+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
218227
* @see self::bind() has to be called first to bind name to socket
219228
* @uses socket_listen()
220229
*/
@@ -237,6 +246,7 @@ public function listen($backlog = 0)
237246
* @param int $type either of PHP_BINARY_READ (the default) or PHP_NORMAL_READ
238247
* @return string
239248
* @throws Exception on error
249+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
240250
* @see self::recv() if you need to pass flags
241251
* @uses socket_read()
242252
*/
@@ -256,6 +266,7 @@ public function read($length, $type = PHP_BINARY_READ)
256266
* @param int $flags
257267
* @return string
258268
* @throws Exception on error
269+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
259270
* @see self::read() if you do not need to pass $flags
260271
* @see self::recvFrom() if your socket is not connect()ed
261272
* @uses socket_recv()
@@ -277,6 +288,7 @@ public function recv($length, $flags)
277288
* @param string $remote reference will be filled with remote/peer address/path
278289
* @return string
279290
* @throws Exception on error
291+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
280292
* @see self::recv() if your socket is connect()ed
281293
* @uses socket_recvfrom()
282294
*/
@@ -296,6 +308,7 @@ public function recvFrom($length, $flags, &$remote)
296308
* @param float|NULL $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit
297309
* @return boolean true = socket ready (read will not block), false = timeout expired, socket is not ready
298310
* @throws Exception on error
311+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
299312
* @uses socket_select()
300313
*/
301314
public function selectRead($sec = 0)
@@ -315,6 +328,7 @@ public function selectRead($sec = 0)
315328
* @param float|NULL $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit
316329
* @return boolean true = socket ready (write will not block), false = timeout expired, socket is not ready
317330
* @throws Exception on error
331+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
318332
* @uses socket_select()
319333
*/
320334
public function selectWrite($sec = 0)
@@ -335,6 +349,7 @@ public function selectWrite($sec = 0)
335349
* @param int $flags
336350
* @return int number of bytes actually written (make sure to check against given buffer length!)
337351
* @throws Exception on error
352+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
338353
* @see self::write() if you do not need to pass $flags
339354
* @see self::sendTo() if your socket is not connect()ed
340355
* @uses socket_send()
@@ -356,6 +371,7 @@ public function send($buffer, $flags)
356371
* @param string $remote remote/peer address/path
357372
* @return int number of bytes actually written
358373
* @throws Exception on error
374+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
359375
* @see self::send() if your socket is connect()ed
360376
* @uses socket_sendto()
361377
*/
@@ -374,6 +390,7 @@ public function sendTo($buffer, $flags, $remote)
374390
* @param boolean $toggle
375391
* @return self $this (chainable)
376392
* @throws Exception on error
393+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
377394
* @uses socket_set_block()
378395
* @uses socket_set_nonblock()
379396
*/
@@ -394,6 +411,7 @@ public function setBlocking($toggle = true)
394411
* @param mixed $optval
395412
* @return self $this (chainable)
396413
* @throws Exception on error
414+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
397415
* @see self::getOption()
398416
* @uses socket_set_option()
399417
*/
@@ -412,6 +430,7 @@ public function setOption($level, $optname, $optval)
412430
* @param int $how 0 = shutdown reading, 1 = shutdown writing, 2 = shutdown reading and writing
413431
* @return self $this (chainable)
414432
* @throws Exception on error
433+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
415434
* @see self::close()
416435
* @uses socket_shutdown()
417436
*/
@@ -430,6 +449,7 @@ public function shutdown($how = 2)
430449
* @param string $buffer
431450
* @return int number of bytes actually written
432451
* @throws Exception on error
452+
* @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
433453
* @see self::send() if you need to pass flags
434454
* @uses socket_write()
435455
*/
@@ -447,6 +467,7 @@ public function write($buffer)
447467
*
448468
* @return int usually either SOCK_STREAM or SOCK_DGRAM
449469
* @throws Exception on error
470+
* @throws \Error PHP 8 only: throws \Error when socket is invalid
450471
* @uses self::getOption()
451472
*/
452473
public function getType()
@@ -469,6 +490,7 @@ public function getType()
469490
*
470491
* @return self $this (chainable)
471492
* @throws Exception if error code is not 0
493+
* @throws \Error PHP 8 only: throws \Error when socket is invalid
472494
* @uses self::getOption() to retrieve and clear current error code
473495
* @uses self::getErrorMessage() to translate error code to
474496
*/

tests/FactoryTest.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ public function testCreateServerIcmp4()
219219
} catch (Exception $e) {
220220
if ($e->getCode() === SOCKET_EPERM) {
221221
// skip if not root
222-
return $this->markTestSkipped('No access to ICMPv4 socket (only root can do so)');
222+
$this->markTestSkipped('No access to ICMPv4 socket (only root can do so)');
223223
}
224224
throw $e;
225225
}
@@ -237,7 +237,7 @@ public function testCreateServerIcmp6()
237237
} catch (Exception $e) {
238238
if ($e->getCode() === SOCKET_EPERM) {
239239
// skip if not root
240-
return $this->markTestSkipped('No access to ICMPv6 socket (only root can do so)');
240+
$this->markTestSkipped('No access to ICMPv6 socket (only root can do so)');
241241
}
242242
throw $e;
243243
}
@@ -311,7 +311,7 @@ public function testCreateIcmp4()
311311
} catch (Exception $e) {
312312
if ($e->getCode() === SOCKET_EPERM) {
313313
// skip if not root
314-
return $this->markTestSkipped('No access to ICMPv4 socket (only root can do so)');
314+
$this->markTestSkipped('No access to ICMPv4 socket (only root can do so)');
315315
}
316316
throw $e;
317317
}
@@ -329,7 +329,7 @@ public function testCreateIcmp6()
329329
} catch (Exception $e) {
330330
if ($e->getCode() === SOCKET_EPERM) {
331331
// skip if not root
332-
return $this->markTestSkipped('No access to ICMPv6 socket (only root can do so)');
332+
$this->markTestSkipped('No access to ICMPv6 socket (only root can do so)');
333333
}
334334
throw $e;
335335
}
@@ -365,6 +365,10 @@ public function testCreateInvalid()
365365
$this->factory->create(0, 1, 2);
366366
} catch (Exception $e) {
367367
return;
368+
} catch (Error $e) {
369+
if (PHP_VERSION_ID >= 80000) {
370+
return;
371+
}
368372
}
369373
$this->fail();
370374
}
@@ -394,6 +398,10 @@ public function testCreatePairInvalid()
394398
$this->factory->createPair(0, 1, 2);
395399
} catch (Exception $e) {
396400
return;
401+
} catch (Error $e) {
402+
if (PHP_VERSION_ID >= 80000) {
403+
return;
404+
}
397405
}
398406
$this->fail();
399407
}

tests/SocketTest.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ public function testConnectGoogle()
2626
$socket = $this->factory->createClient('www.google.com:80');
2727

2828
$this->assertInstanceOf('Socket\Raw\Socket', $socket);
29-
$this->assertEquals('resource', gettype($socket->getResource()));
29+
30+
if (PHP_VERSION_ID >= 80000) {
31+
$this->assertInstanceOf('Socket', $socket->getResource());
32+
} else {
33+
$this->assertEquals('resource', gettype($socket->getResource()));
34+
}
3035

3136
// connecting from local address:
3237
$address = $socket->getSockName();
@@ -220,6 +225,10 @@ public function testServerNonBlocking()
220225
*/
221226
public function testServerNonBlockingAcceptNobody(Socket $server)
222227
{
228+
if (PHP_VERSION_ID >= 80000) {
229+
$this->markTestIncomplete('Causes SEGFAULTs on PHP 8');
230+
}
231+
223232
try {
224233
$server->accept();
225234
$this->fail('accept() MUST throw an exception');
@@ -262,7 +271,11 @@ public function testBindThrowsWhenSocketIsAlreadyClosed()
262271
$socket = $this->factory->createTcp4();
263272
$socket->close();
264273

265-
$this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ENOTSOCK);
274+
if (PHP_VERSION_ID >= 80000) {
275+
$this->setExpectedException('Error', 'has already been closed');
276+
} else {
277+
$this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ENOTSOCK);
278+
}
266279
$socket->bind('127.0.0.1:0');
267280
}
268281

@@ -271,7 +284,11 @@ public function testAssertAliveThrowsWhenSocketIsAlreadyClosed()
271284
$socket = $this->factory->createTcp4();
272285
$socket->close();
273286

274-
$this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ENOTSOCK);
287+
if (PHP_VERSION_ID >= 80000) {
288+
$this->setExpectedException('Error', 'has already been closed');
289+
} else {
290+
$this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ENOTSOCK);
291+
}
275292
$socket->assertAlive();
276293
}
277294

0 commit comments

Comments
 (0)