Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support PHP 8, use Socket object instead of socket resource #61

Merged
merged 1 commit into from
Nov 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ jobs:
os:
- ubuntu-latest
- windows-latest
php:
php:
- 8.0
- 7.4
- 7.3
- 7.2
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions src/Exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
*/
Expand Down
28 changes: 25 additions & 3 deletions src/Socket.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Socket
/**
* reference to actual socket resource
*
* @var resource
* @var \Socket|resource
*/
private $resource;

Expand All @@ -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)
Expand All @@ -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()
{
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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()
*/
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
*/
Expand All @@ -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()
*/
Expand All @@ -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()
Expand All @@ -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()
*/
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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()
*/
Expand All @@ -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()
*/
Expand All @@ -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()
*/
Expand All @@ -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()
*/
Expand All @@ -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()
*/
Expand All @@ -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()
Expand All @@ -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
*/
Expand Down
16 changes: 12 additions & 4 deletions tests/FactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand Down
23 changes: 20 additions & 3 deletions tests/SocketTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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');
}

Expand All @@ -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();
}

Expand Down