diff --git a/README.md b/README.md index ecaf575..b4f432b 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,9 @@ The following example code demonstrates how this library can be used to send a plaintext HTTP request to google.com through a remote SSH server: ```php -$loop = React\EventLoop\Factory::create(); +$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com'); -$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com', $loop); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); @@ -92,8 +91,6 @@ $connector->connect('tcp://google.com:80')->then(function (React\Socket\Connecti echo '[DONE]'; }); }, 'printf'); - -$loop->run(); ``` See also the [examples](examples). @@ -121,17 +118,22 @@ systems, you may simply install it like this: $ sudo apt install openssh-client ``` -Its constructor simply accepts an SSH proxy server URL and a loop to bind to: +Its constructor simply accepts an SSH proxy server URL: ```php -$loop = React\EventLoop\Factory::create(); -$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com'); ``` The proxy URL may or may not contain a scheme and port definition. The default port will be `22` for SSH, but you may have to use a custom port depending on your SSH server setup. +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + Keep in mind that this class is implemented as a lightweight process wrapper around the `ssh` client binary and that it will spawn one `ssh` process for each connection. If you open more connections, it will spawn one `ssh` process for @@ -160,7 +162,7 @@ higher-level component: ```diff - $acme = new AcmeApi($connector); -+ $proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com', $loop); ++ $proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com'); + $acme = new AcmeApi($proxy); ``` @@ -189,17 +191,22 @@ simply install it like this: $ sudo apt install openssh-client ``` -Its constructor simply accepts an SSH proxy server URL and a loop to bind to: +Its constructor simply accepts an SSH proxy server URL: ```php -$loop = React\EventLoop\Factory::create(); -$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com'); ``` The proxy URL may or may not contain a scheme and port definition. The default port will be `22` for SSH, but you may have to use a custom port depending on your SSH server setup. +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + Keep in mind that this class is implemented as a lightweight process wrapper around the `ssh` client binary and that it will spawn one `ssh` process for multiple connections. This process will take some time to create a new SSH @@ -216,7 +223,7 @@ to use multiple instances of this class to connect to different SSH proxy servers, you may optionally pass a unique bind address like this: ```php -$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com?bind=127.1.1.1:1081', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com?bind=127.1.1.1:1081',); ``` > *Security note for multi-user systems*: This class will spawn the SSH client @@ -244,7 +251,7 @@ higher-level component: ```diff - $acme = new AcmeApi($connector); -+ $proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com', $loop); ++ $proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com'); + $acme = new AcmeApi($proxy); ``` @@ -260,9 +267,9 @@ a streaming plain TCP/IP connection on the `SshProcessConnector` or `SshSocksCon and use any higher level protocol like so: ```php -$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com'); // or -$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com'); $proxy->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write("EHLO local\r\n"); @@ -276,11 +283,11 @@ You can either use the `SshProcessConnector` or `SshSocksConnector` directly or may want to wrap this connector in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector): ```php -$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com'); // or -$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); @@ -309,9 +316,9 @@ ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector) or the low-level [`SecureConnector`](https://github.com/reactphp/socket#secureconnector): ```php -$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); @@ -341,14 +348,14 @@ In order to send HTTP requests, you first have to add a dependency for This allows you to send both plain HTTP and TLS-encrypted HTTPS requests like this: ```php -$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); -$browser = new React\Http\Browser($loop, $connector); +$browser = new React\Http\Browser(null, $connector); $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders(), (string) $response->getBody()); @@ -378,11 +385,10 @@ the above SSH proxy server setup, so we can access a firewalled MySQL database server through an SSH tunnel. Here's the gist: ```php -$loop = React\EventLoop\Factory::create(); -$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com'); $uri = 'test:test@localhost/test'; -$factory = new React\MySQL\Factory($loop, $proxy); +$factory = new React\MySQL\Factory(null, $proxy); $connection = $factory->createLazyConnection($uri); $connection->query('SELECT * FROM book')->then( @@ -395,8 +401,6 @@ $connection->query('SELECT * FROM book')->then( ); $connection->quit(); - -$loop->run(); ``` See also [example #21](examples) for more details. @@ -427,11 +431,11 @@ It provides the same `connect()` method, but will automatically reject the underlying connection attempt if it takes too long: ```php -$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com'); // or -$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false, 'timeout' => 3.0 @@ -473,11 +477,11 @@ Given that remote DNS resolution is assumed to be the preferred mode, all other examples explicitly disable DNS resolution like this: ```php -$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com'); // or -$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); @@ -486,12 +490,12 @@ $connector = new React\Socket\Connector($loop, array( If you want to explicitly use *local DNS resolution*, you can use the following code: ```php -$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector('user@example.com'); // or -$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com'); // set up Connector which uses Google's public DNS (8.8.8.8) -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => '8.8.8.8' )); @@ -525,9 +529,9 @@ If your SSH proxy server requires password authentication, you may pass the username and password as part of the SSH proxy server URL like this: ```php -$proxy = new Clue\React\SshProxy\SshProcessConnector('user:pass@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector('user:pass@example.com'); // or -$proxy = new Clue\React\SshProxy\SshSocksConnector('user:pass@example.com', $loop); +$proxy = new Clue\React\SshProxy\SshSocksConnector('user:pass@example.com'); ``` For this to work, you will have to have the `sshpass` binary installed. On @@ -545,8 +549,7 @@ $user = 'he:llo'; $pass = 'p@ss'; $proxy = new Clue\React\SshProxy\SshProcessConnector( - rawurlencode($user) . ':' . rawurlencode($pass) . '@example.com:2222', - $loop + rawurlencode($user) . ':' . rawurlencode($pass) . '@example.com:2222' ); ``` diff --git a/composer.json b/composer.json index 9135637..a312692 100644 --- a/composer.json +++ b/composer.json @@ -21,15 +21,15 @@ "php": ">=5.3", "clue/socks-react": "^1.0", "react/child-process": "^0.6", - "react/event-loop": "^1.0 || ^0.5", + "react/event-loop": "^1.2", "react/promise": "^2.1 || ^1.2.1", - "react/socket": "^1.1", - "react/stream": "^1.0 || ^0.7.2" + "react/socket": "^1.8", + "react/stream": "^1.2" }, "require-dev": { "clue/block-react": "^1.3", "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36", - "react/http": "^1.0", - "react/mysql": "^0.5.3" + "react/http": "^1.4", + "react/mysql": "^0.5.5" } } diff --git a/examples/01-https-request.php b/examples/01-https-request.php index 9025d77..7def3b3 100644 --- a/examples/01-https-request.php +++ b/examples/01-https-request.php @@ -13,21 +13,18 @@ $url = getenv('SSH_PROXY') !== false ? getenv('SSH_PROXY') : 'ssh://localhost:22'; -$loop = React\EventLoop\Factory::create(); -$proxy = new Clue\React\SshProxy\SshProcessConnector($url, $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector($url); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false )); -$browser = new React\Http\Browser($loop, $connector); +$browser = new React\Http\Browser(null, $connector); $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders(), (string) $response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/02-optional-proxy-https-request.php b/examples/02-optional-proxy-https-request.php index 058fef9..d83fc16 100644 --- a/examples/02-optional-proxy-https-request.php +++ b/examples/02-optional-proxy-https-request.php @@ -11,26 +11,22 @@ require __DIR__ . '/../vendor/autoload.php'; -$loop = React\EventLoop\Factory::create(); - // SSH_PROXY environment given? use this as the proxy URL +$connector = null; if (getenv('SSH_PROXY') !== false) { - $proxy = new Clue\React\SshProxy\SshProcessConnector(getenv('SSH_PROXY'), $loop); - $connector = new React\Socket\Connector($loop, array( + $proxy = new Clue\React\SshProxy\SshProcessConnector(getenv('SSH_PROXY')); + + $connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false )); -} else { - $connector = new React\Socket\Connector($loop); } -$browser = new React\Http\Browser($loop, $connector); +$browser = new React\Http\Browser(null, $connector); $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders(), (string) $response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/11-proxy-raw-http-protocol.php b/examples/11-proxy-raw-http-protocol.php index 52bc885..7964914 100644 --- a/examples/11-proxy-raw-http-protocol.php +++ b/examples/11-proxy-raw-http-protocol.php @@ -16,10 +16,9 @@ $url = getenv('SSH_PROXY') !== false ? getenv('SSH_PROXY') : 'ssh://localhost:22'; -$loop = React\EventLoop\Factory::create(); +$proxy = new Clue\React\SshProxy\SshProcessConnector($url); -$proxy = new Clue\React\SshProxy\SshProcessConnector($url, $loop); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false @@ -33,5 +32,3 @@ }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/12-optional-proxy-raw-http-protocol.php b/examples/12-optional-proxy-raw-http-protocol.php index 7390c1f..84a78e7 100644 --- a/examples/12-optional-proxy-raw-http-protocol.php +++ b/examples/12-optional-proxy-raw-http-protocol.php @@ -18,18 +18,17 @@ require __DIR__ . '/../vendor/autoload.php'; -$loop = React\EventLoop\Factory::create(); - // SSH_PROXY environment given? use this as the proxy URL if (getenv('SSH_PROXY') !== false) { - $proxy = new Clue\React\SshProxy\SshProcessConnector(getenv('SSH_PROXY'), $loop); - $connector = new React\Socket\Connector($loop, array( + $proxy = new Clue\React\SshProxy\SshProcessConnector(getenv('SSH_PROXY')); + + $connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false )); } else { - $connector = new React\Socket\Connector($loop); + $connector = new React\Socket\Connector(); } $connector->connect('tcp://google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { @@ -40,5 +39,3 @@ }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/21-proxy-raw-https-protocol.php b/examples/21-proxy-raw-https-protocol.php index 5cd6774..41028c3 100644 --- a/examples/21-proxy-raw-https-protocol.php +++ b/examples/21-proxy-raw-https-protocol.php @@ -16,10 +16,9 @@ $url = getenv('SSH_PROXY') !== false ? getenv('SSH_PROXY') : 'ssh://localhost:22'; -$loop = React\EventLoop\Factory::create(); +$proxy = new Clue\React\SshProxy\SshSocksConnector($url); -$proxy = new Clue\React\SshProxy\SshSocksConnector($url, $loop); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false @@ -33,5 +32,3 @@ }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/22-optional-proxy-raw-https-protocol.php b/examples/22-optional-proxy-raw-https-protocol.php index 75f2b63..645fc1a 100644 --- a/examples/22-optional-proxy-raw-https-protocol.php +++ b/examples/22-optional-proxy-raw-https-protocol.php @@ -18,18 +18,17 @@ require __DIR__ . '/../vendor/autoload.php'; -$loop = React\EventLoop\Factory::create(); - // SSH_PROXY environment given? use this as the proxy URL if (getenv('SSH_PROXY') !== false) { - $proxy = new Clue\React\SshProxy\SshProcessConnector(getenv('SSH_PROXY'), $loop); - $connector = new React\Socket\Connector($loop, array( + $proxy = new Clue\React\SshProxy\SshProcessConnector(getenv('SSH_PROXY')); + + $connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false )); } else { - $connector = new React\Socket\Connector($loop); + $connector = new React\Socket\Connector(); } $connector->connect('tls://google.com:443')->then(function (React\Socket\ConnectionInterface $connection) { @@ -40,5 +39,3 @@ }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/31-mysql-ssh-tunnel.php b/examples/31-mysql-ssh-tunnel.php index d5f9f8e..63ee54d 100644 --- a/examples/31-mysql-ssh-tunnel.php +++ b/examples/31-mysql-ssh-tunnel.php @@ -16,18 +16,15 @@ require __DIR__ . '/../vendor/autoload.php'; -$loop = React\EventLoop\Factory::create(); - $url = getenv('SSH_PROXY') !== false ? getenv('SSH_PROXY') : 'ssh://localhost:22'; -$proxy = new Clue\React\SshProxy\SshProcessConnector($url, $loop); +$proxy = new Clue\React\SshProxy\SshProcessConnector($url); $url = getenv('MYSQL_LOGIN') !== false ? getenv('MYSQL_LOGIN') : 'user:pass@localhost'; -$factory = new React\MySQL\Factory($loop, $proxy); +$factory = new React\MySQL\Factory(null, $proxy); $client = $factory->createLazyConnection($url); $client->query('SELECT * FROM (SELECT "foo" UNION SELECT "bar") data')->then(function (React\MySQL\QueryResult $query) { var_dump($query->resultRows); }, 'printf'); -$client->quit(); -$loop->run(); +$client->quit(); diff --git a/src/SshProcessConnector.php b/src/SshProcessConnector.php index bf10b0f..389c687 100644 --- a/src/SshProcessConnector.php +++ b/src/SshProcessConnector.php @@ -4,6 +4,7 @@ use Clue\React\SshProxy\Io\CompositeConnection; use Clue\React\SshProxy\Io\LineSeparatedReader; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Promise\Deferred; use React\Socket\ConnectorInterface; @@ -11,6 +12,8 @@ class SshProcessConnector implements ConnectorInterface { private $cmd; + + /** @var LoopInterface */ private $loop; private $debug = false; @@ -25,11 +28,17 @@ class SshProcessConnector implements ConnectorInterface * (which may need to be installed) and this password may leak to the * process list if other users have access to your system. * + * This class takes an optional `LoopInterface|null $loop` parameter that can be used to + * pass the event loop instance to use for this object. You can use a `null` value + * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). + * This value SHOULD NOT be given unless you're sure you want to explicitly use a + * given event loop instance. + * * @param string $uri - * @param LoopInterface $loop + * @param ?LoopInterface $loop * @throws \InvalidArgumentException */ - public function __construct($uri, LoopInterface $loop) + public function __construct($uri, LoopInterface $loop = null) { // URI must use optional ssh:// scheme, must contain host and neither pass nor target must start with dash $parts = \parse_url((\strpos($uri, '://') === false ? 'ssh://' : '') . $uri); @@ -54,7 +63,7 @@ public function __construct($uri, LoopInterface $loop) $this->cmd .= '-p ' . $parts['port'] . ' '; } $this->cmd .= \escapeshellarg($target); - $this->loop = $loop; + $this->loop = $loop ?: Loop::get(); } public function connect($uri) diff --git a/src/SshSocksConnector.php b/src/SshSocksConnector.php index 278c096..886c19c 100644 --- a/src/SshSocksConnector.php +++ b/src/SshSocksConnector.php @@ -2,18 +2,21 @@ namespace Clue\React\SshProxy; +use Clue\React\Socks\Client; use Clue\React\SshProxy\Io\LineSeparatedReader; -use React\EventLoop\LoopInterface; use React\ChildProcess\Process; +use React\EventLoop\Loop; +use React\EventLoop\LoopInterface; use React\Promise\Deferred; use React\Socket\ConnectorInterface; -use Clue\React\Socks\Client; use React\Socket\TcpConnector; use React\Socket\ConnectionInterface; class SshSocksConnector implements ConnectorInterface { private $cmd; + + /** @var LoopInterface */ private $loop; private $debug = false; @@ -36,11 +39,17 @@ class SshSocksConnector implements ConnectorInterface * (which may need to be installed) and this password may leak to the * process list if other users have access to your system. * + * This class takes an optional `LoopInterface|null $loop` parameter that can be used to + * pass the event loop instance to use for this object. You can use a `null` value + * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). + * This value SHOULD NOT be given unless you're sure you want to explicitly use a + * given event loop instance. + * * @param string $uri - * @param LoopInterface $loop + * @param ?LoopInterface $loop * @throws \InvalidArgumentException */ - public function __construct($uri, LoopInterface $loop) + public function __construct($uri, LoopInterface $loop = null) { // URI must use optional ssh:// scheme, must contain host and neither pass nor target must start with dash $parts = \parse_url((\strpos($uri, '://') === false ? 'ssh://' : '') . $uri); @@ -75,9 +84,9 @@ public function __construct($uri, LoopInterface $loop) $this->bind = $args['bind']; } - $this->socks = new Client('socks4://' . $this->bind, new TcpConnector($loop)); + $this->loop = $loop ?: Loop::get(); + $this->socks = new Client('socks4://' . $this->bind, new TcpConnector($this->loop)); $this->cmd .= '-D ' . \escapeshellarg($this->bind) . ' ' . \escapeshellarg($target); - $this->loop = $loop; } public function connect($uri) diff --git a/tests/SshProcessConnectorTest.php b/tests/SshProcessConnectorTest.php index 01cdfc9..d3a56d2 100644 --- a/tests/SshProcessConnectorTest.php +++ b/tests/SshProcessConnectorTest.php @@ -6,6 +6,17 @@ class SshProcessConnectorTest extends TestCase { + public function testConstructWithoutLoopAssignsLoopAutomatically() + { + $connector = new SshProcessConnector('host'); + + $ref = new \ReflectionProperty($connector, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($connector); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + } + public function testConstructorAcceptsUri() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); diff --git a/tests/SshSocksConnectorTest.php b/tests/SshSocksConnectorTest.php index 2d00f16..62bb548 100644 --- a/tests/SshSocksConnectorTest.php +++ b/tests/SshSocksConnectorTest.php @@ -7,6 +7,17 @@ class SshSocksConnectorTest extends TestCase { + public function testConstructWithoutLoopAssignsLoopAutomatically() + { + $connector = new SshSocksConnector('host'); + + $ref = new \ReflectionProperty($connector, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($connector); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + } + public function testConstructorAcceptsUri() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();