diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80993d1..b83ce49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,8 @@ jobs: os: - ubuntu-latest - windows-latest - php: + php: + - 8.0 - 7.4 - 7.3 - 7.2 diff --git a/README.md b/README.md index 9855d15..63b630a 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP extensions besides `ext-sockets` and supports running on legacy PHP 5.3 through -current PHP 7+. +current PHP 8+. It's *highly recommended to use PHP 7+* for this project. ## Tests diff --git a/src/Exception.php b/src/Exception.php index 452643e..bdabf78 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -9,7 +9,7 @@ class Exception extends RuntimeException /** * Create an Exception after a socket operation on the given $resource failed * - * @param resource $resource + * @param \Socket|resource $resource * @param string $messagePrefix * @return self * @uses socket_last_error() to get last socket error code @@ -18,7 +18,13 @@ class Exception extends RuntimeException */ public static function createFromSocketResource($resource, $messagePrefix = 'Socket operation failed') { - if (is_resource($resource)) { + if (PHP_VERSION_ID >= 80000) { + try { + $code = socket_last_error($resource); + } catch (\Error $e) { + $code = SOCKET_ENOTSOCK; + } + } elseif (is_resource($resource)) { $code = socket_last_error($resource); socket_clear_error($resource); } else { diff --git a/src/Factory.php b/src/Factory.php index d8ab5ef..bd72176 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -170,6 +170,7 @@ public function createIcmp6() * @param int $protocol * @return \Socket\Raw\Socket * @throws Exception if creating socket fails + * @throws \Error PHP 8 only: throws \Error when arguments are invalid * @uses socket_create() */ public function create($domain, $type, $protocol) @@ -189,6 +190,7 @@ public function create($domain, $type, $protocol) * @param int $protocol * @return \Socket\Raw\Socket[] * @throws Exception if creating pair of sockets fails + * @throws \Error PHP 8 only: throws \Error when arguments are invalid * @uses socket_create_pair() */ public function createPair($domain, $type, $protocol) @@ -207,6 +209,7 @@ public function createPair($domain, $type, $protocol) * @param int $backlog * @return \Socket\Raw\Socket * @throws Exception if creating listening socket fails + * @throws \Error PHP 8 only: throws \Error when arguments are invalid * @uses socket_create_listen() * @see self::createServer() as an alternative to bind to specific IP, IPv6, UDP, UNIX, UGP */ diff --git a/src/Socket.php b/src/Socket.php index e86c195..514bd97 100644 --- a/src/Socket.php +++ b/src/Socket.php @@ -13,7 +13,7 @@ class Socket /** * reference to actual socket resource * - * @var resource + * @var \Socket|resource */ private $resource; @@ -22,7 +22,7 @@ class Socket * * should usually not be called manually, see Factory * - * @param resource $resource + * @param \Socket|resource $resource * @see Factory as the preferred (and simplest) way to construct socket instances */ public function __construct($resource) @@ -33,7 +33,7 @@ public function __construct($resource) /** * get actual socket resource * - * @return resource + * @return \Socket|resource returns the socket resource (a `Socket` object as of PHP 8) */ public function getResource() { @@ -45,6 +45,7 @@ public function getResource() * * @return \Socket\Raw\Socket new connected socket used for communication * @throws Exception on error, if this is not a listening socket or there's no connection pending + * @throws \Error PHP 8 only: throws \Error when socket is invalid * @see self::selectRead() to check if this listening socket can accept() * @see Factory::createServer() to create a listening socket * @see self::listen() has to be called first @@ -67,6 +68,7 @@ public function accept() * @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path * @return self $this (chainable) * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @uses socket_bind() */ public function bind($address) @@ -85,6 +87,7 @@ public function bind($address) * its socket resource remains closed and most further operations will fail! * * @return self $this (chainable) + * @throws \Error PHP 8 only: throws \Error when socket is invalid * @see self::shutdown() should be called before closing socket * @uses socket_close() */ @@ -100,6 +103,7 @@ public function close() * @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path * @return self $this (chainable) * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @uses socket_connect() */ public function connect($address) @@ -126,6 +130,7 @@ public function connect($address) * @param float $timeout maximum time to wait (in seconds) * @return self $this (chainable) * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @uses self::setBlocking() to enable non-blocking mode * @uses self::connect() to initiate the connection * @uses self::selectWrite() to wait for the connection to complete @@ -166,6 +171,7 @@ public function connectTimeout($address, $timeout) * @param int $optname * @return mixed * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @uses socket_get_option() */ public function getOption($level, $optname) @@ -182,6 +188,7 @@ public function getOption($level, $optname) * * @return string * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket is invalid * @uses socket_getpeername() */ public function getPeerName() @@ -198,6 +205,7 @@ public function getPeerName() * * @return string * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket is invalid * @uses socket_getsockname() */ public function getSockName() @@ -215,6 +223,7 @@ public function getSockName() * @param int $backlog maximum number of incoming connections to be queued * @return self $this (chainable) * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @see self::bind() has to be called first to bind name to socket * @uses socket_listen() */ @@ -237,6 +246,7 @@ public function listen($backlog = 0) * @param int $type either of PHP_BINARY_READ (the default) or PHP_NORMAL_READ * @return string * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @see self::recv() if you need to pass flags * @uses socket_read() */ @@ -256,6 +266,7 @@ public function read($length, $type = PHP_BINARY_READ) * @param int $flags * @return string * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @see self::read() if you do not need to pass $flags * @see self::recvFrom() if your socket is not connect()ed * @uses socket_recv() @@ -277,6 +288,7 @@ public function recv($length, $flags) * @param string $remote reference will be filled with remote/peer address/path * @return string * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @see self::recv() if your socket is connect()ed * @uses socket_recvfrom() */ @@ -296,6 +308,7 @@ public function recvFrom($length, $flags, &$remote) * @param float|NULL $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit * @return boolean true = socket ready (read will not block), false = timeout expired, socket is not ready * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @uses socket_select() */ public function selectRead($sec = 0) @@ -315,6 +328,7 @@ public function selectRead($sec = 0) * @param float|NULL $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit * @return boolean true = socket ready (write will not block), false = timeout expired, socket is not ready * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @uses socket_select() */ public function selectWrite($sec = 0) @@ -335,6 +349,7 @@ public function selectWrite($sec = 0) * @param int $flags * @return int number of bytes actually written (make sure to check against given buffer length!) * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @see self::write() if you do not need to pass $flags * @see self::sendTo() if your socket is not connect()ed * @uses socket_send() @@ -356,6 +371,7 @@ public function send($buffer, $flags) * @param string $remote remote/peer address/path * @return int number of bytes actually written * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @see self::send() if your socket is connect()ed * @uses socket_sendto() */ @@ -374,6 +390,7 @@ public function sendTo($buffer, $flags, $remote) * @param boolean $toggle * @return self $this (chainable) * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @uses socket_set_block() * @uses socket_set_nonblock() */ @@ -394,6 +411,7 @@ public function setBlocking($toggle = true) * @param mixed $optval * @return self $this (chainable) * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @see self::getOption() * @uses socket_set_option() */ @@ -412,6 +430,7 @@ public function setOption($level, $optname, $optval) * @param int $how 0 = shutdown reading, 1 = shutdown writing, 2 = shutdown reading and writing * @return self $this (chainable) * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @see self::close() * @uses socket_shutdown() */ @@ -430,6 +449,7 @@ public function shutdown($how = 2) * @param string $buffer * @return int number of bytes actually written * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid * @see self::send() if you need to pass flags * @uses socket_write() */ @@ -447,6 +467,7 @@ public function write($buffer) * * @return int usually either SOCK_STREAM or SOCK_DGRAM * @throws Exception on error + * @throws \Error PHP 8 only: throws \Error when socket is invalid * @uses self::getOption() */ public function getType() @@ -469,6 +490,7 @@ public function getType() * * @return self $this (chainable) * @throws Exception if error code is not 0 + * @throws \Error PHP 8 only: throws \Error when socket is invalid * @uses self::getOption() to retrieve and clear current error code * @uses self::getErrorMessage() to translate error code to */ diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index acea175..81a9ac3 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -219,7 +219,7 @@ public function testCreateServerIcmp4() } catch (Exception $e) { if ($e->getCode() === SOCKET_EPERM) { // skip if not root - return $this->markTestSkipped('No access to ICMPv4 socket (only root can do so)'); + $this->markTestSkipped('No access to ICMPv4 socket (only root can do so)'); } throw $e; } @@ -237,7 +237,7 @@ public function testCreateServerIcmp6() } catch (Exception $e) { if ($e->getCode() === SOCKET_EPERM) { // skip if not root - return $this->markTestSkipped('No access to ICMPv6 socket (only root can do so)'); + $this->markTestSkipped('No access to ICMPv6 socket (only root can do so)'); } throw $e; } @@ -311,7 +311,7 @@ public function testCreateIcmp4() } catch (Exception $e) { if ($e->getCode() === SOCKET_EPERM) { // skip if not root - return $this->markTestSkipped('No access to ICMPv4 socket (only root can do so)'); + $this->markTestSkipped('No access to ICMPv4 socket (only root can do so)'); } throw $e; } @@ -329,7 +329,7 @@ public function testCreateIcmp6() } catch (Exception $e) { if ($e->getCode() === SOCKET_EPERM) { // skip if not root - return $this->markTestSkipped('No access to ICMPv6 socket (only root can do so)'); + $this->markTestSkipped('No access to ICMPv6 socket (only root can do so)'); } throw $e; } @@ -365,6 +365,10 @@ public function testCreateInvalid() $this->factory->create(0, 1, 2); } catch (Exception $e) { return; + } catch (Error $e) { + if (PHP_VERSION_ID >= 80000) { + return; + } } $this->fail(); } @@ -394,6 +398,10 @@ public function testCreatePairInvalid() $this->factory->createPair(0, 1, 2); } catch (Exception $e) { return; + } catch (Error $e) { + if (PHP_VERSION_ID >= 80000) { + return; + } } $this->fail(); } diff --git a/tests/SocketTest.php b/tests/SocketTest.php index ca73603..8589692 100644 --- a/tests/SocketTest.php +++ b/tests/SocketTest.php @@ -26,7 +26,12 @@ public function testConnectGoogle() $socket = $this->factory->createClient('www.google.com:80'); $this->assertInstanceOf('Socket\Raw\Socket', $socket); - $this->assertEquals('resource', gettype($socket->getResource())); + + if (PHP_VERSION_ID >= 80000) { + $this->assertInstanceOf('Socket', $socket->getResource()); + } else { + $this->assertEquals('resource', gettype($socket->getResource())); + } // connecting from local address: $address = $socket->getSockName(); @@ -220,6 +225,10 @@ public function testServerNonBlocking() */ public function testServerNonBlockingAcceptNobody(Socket $server) { + if (PHP_VERSION_ID >= 80000) { + $this->markTestIncomplete('Causes SEGFAULTs on PHP 8'); + } + try { $server->accept(); $this->fail('accept() MUST throw an exception'); @@ -262,7 +271,11 @@ public function testBindThrowsWhenSocketIsAlreadyClosed() $socket = $this->factory->createTcp4(); $socket->close(); - $this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ENOTSOCK); + if (PHP_VERSION_ID >= 80000) { + $this->setExpectedException('Error', 'has already been closed'); + } else { + $this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ENOTSOCK); + } $socket->bind('127.0.0.1:0'); } @@ -271,7 +284,11 @@ public function testAssertAliveThrowsWhenSocketIsAlreadyClosed() $socket = $this->factory->createTcp4(); $socket->close(); - $this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ENOTSOCK); + if (PHP_VERSION_ID >= 80000) { + $this->setExpectedException('Error', 'has already been closed'); + } else { + $this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ENOTSOCK); + } $socket->assertAlive(); }