diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88e99d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor +composer.lock \ No newline at end of file diff --git a/composer.json b/composer.json index aa20e71..13654ee 100755 --- a/composer.json +++ b/composer.json @@ -11,12 +11,23 @@ { "name": "Evseev Nikolay", "email": "evseevnn@gmail.com" + }, + { + "name": "Aleksandar Babic", + "email": "salebab@gmail.com" } ], "require": { "php": ">=5.4.0" }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, "autoload": { "psr-4": { "Cassandra\\": "src/" } + }, + + "autoload-dev" : { + "psr-4": { "Cassandra\\": "tests/"} } } diff --git a/phpunit.php b/phpunit.php new file mode 100644 index 0000000..39a1434 --- /dev/null +++ b/phpunit.php @@ -0,0 +1,5 @@ + + + + ./tests/ + + + + + src + + vendor + + + + \ No newline at end of file diff --git a/src/Connection/Node.php b/src/Connection/Node.php index aefdbe0..b09c0de 100644 --- a/src/Connection/Node.php +++ b/src/Connection/Node.php @@ -45,25 +45,20 @@ public function __construct($options) { } /** - * + * Create connection and return socket resource + * * @throws Exception * @return resource */ public function getConnection() { - if (!empty($this->socket)) return $this->socket; - $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - - if ($this->socket === false) - throw new Exception(socket_strerror(socket_last_error())); - - socket_set_option($this->socket, getprotobyname('TCP'), TCP_NODELAY, 1); - - foreach($this->_options['socket'] as $optname => $optval) - socket_set_option($this->socket, SOL_SOCKET, $optname, $optval); - - if (!socket_connect($this->socket, $this->_options['host'], $this->_options['port'])) - throw new Exception("Unable to connect to Cassandra node: {$this->_options['host']}:{$this->_options['port']}"); + if(!$this->isConnected()) { + try { + $this->connect(); + } catch(\Exception $e) { + throw new Exception("Unable to connect to Cassandra node: {$this->_options['host']}:{$this->_options['port']}. Reason: ". $e->getMessage(), 0, $e); + } + } return $this->socket; } @@ -71,7 +66,85 @@ public function getConnection() { /** * @return array */ - public function getOptions() { + public function getOptions() + { return $this->_options; } + + /** + * Connect to host/port + * + * @throws Exception + */ + public function connect() + { + $this->createConnection(); + $this->setConnectionOptions(); + + $result = @socket_connect($this->socket, $this->_options['host'], $this->_options['port']); + + if($result === false) { + throw new Exception("Unable to connect: " . socket_strerror(socket_last_error($this->socket))); + } + } + + /** + * Disconnect + */ + public function disconnect() + { + if(is_resource($this->socket)) { + @socket_close($this->socket); + } + + $this->socket = null; + } + + /** + * @return bool + */ + public function isConnected() + { + return is_resource($this->socket); + } + + + /** + * @throws Exception + */ + private function createConnection() + { + $this->socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + + if($this->socket === false) { + throw new Exception("Unable to create socket: ". socket_strerror(socket_last_error())); + } + } + + /** + * @throws Exception + */ + private function setConnectionOptions() + { + $this->setConnectionOption(getprotobyname('tcp'), TCP_NODELAY, 1); + + foreach($this->_options['socket'] as $name => $value) { + $this->setConnectionOption(SOL_SOCKET, $name, $value); + } + } + + /** + * @param int $level + * @param int $name + * @param mixed $value + * @throws Exception + */ + private function setConnectionOption($level, $name, $value) + { + $result = @socket_set_option($this->socket, $level, $name, $value); + + if($result === false) { + throw new Exception("Unable to set socket option: $level, $name"); + } + } } diff --git a/tests/Connection/NodeTest.php b/tests/Connection/NodeTest.php new file mode 100644 index 0000000..62ba0e7 --- /dev/null +++ b/tests/Connection/NodeTest.php @@ -0,0 +1,107 @@ +listen(19042); + } + + public static function tearDownAfterClass() + { + self::$server->shutdown(); + } + + public function testNewInstance() + { + $node = new Node("localhost:19042"); + + $this->assertInstanceOf('Cassandra\Connection\Node', $node); + + return $node; + } + + /** + * @param Node $node + * @depends testNewInstance + */ + public function testIsConnected(Node $node) + { + $this->assertFalse($node->isConnected()); + + $node->connect(); + + $this->assertTrue($node->isConnected()); + } + + + /** + * @depends testNewInstance + * @param Node $node + */ + public function testGetOptions(Node $node) + { + $this->assertArrayHasKey("host", $node->getOptions()); + $this->assertArrayHasKey("port", $node->getOptions()); + } + + /** + * @depends testNewInstance + * @param Node $node + */ + public function testGetConnectionReturnsResource(Node $node) + { + $connection = $node->getConnection(); + + $this->assertTrue(is_resource($connection)); + } + + /** + * @throws Exception + */ + public function testGetConnectionException() + { + $node = new Node("invalidhost:9042"); + + $this->setExpectedException('Cassandra\Connection\Exception', "invalidhost:9042"); + + $node->getConnection(); + $node->disconnect(); + } + + public function testInvalidOptions() + { + $node = new Node([ + 'host' => 'localhost', + 'port' => 19042, + 'socket' => [ + SO_RCVTIMEO => -1, + ] + ]); + + $this->setExpectedException('Cassandra\Connection\Exception', "Unable to set socket option"); + + $node->connect(); + } + + /** + * + * @depends testNewInstance + * @param Node $node + */ + public function testDisconnect(Node $node) + { + $node->disconnect(); + + $this->assertFalse($node->isConnected()); + } +} diff --git a/tests/Stubs/ListeningServerStub.php b/tests/Stubs/ListeningServerStub.php new file mode 100644 index 0000000..89fc2dc --- /dev/null +++ b/tests/Stubs/ListeningServerStub.php @@ -0,0 +1,27 @@ +sock = socket_create(AF_INET, SOCK_STREAM, 0); + + // Bind the socket to an address/port + if(!socket_bind($this->sock, 'localhost', $port)) { + throw new \RuntimeException('Could not bind to address'); + } + + socket_set_nonblock($this->sock); + + // Start listening for connections + socket_listen($this->sock); + } + + + public function shutdown() + { + socket_shutdown($this->sock); + } +} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..6b28a89 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,6 @@ +