diff --git a/lib/Ratchet/Application/ApplicationInterface.php b/lib/Ratchet/Application/ApplicationInterface.php index a5c37a3..91b039c 100644 --- a/lib/Ratchet/Application/ApplicationInterface.php +++ b/lib/Ratchet/Application/ApplicationInterface.php @@ -1,12 +1,44 @@ _app = $application; - $this->_connections = new \ArrayIterator(array()); + $this->_app = $application; } /* @@ -54,7 +48,7 @@ class App implements ApplicationInterface { * @todo Consider making the 4kb listener changable */ public function run(SocketInterface $host, $address = '127.0.0.1', $port = 1025) { - $this->_master = $host; + $this->_connections[$host->getResource()] = new Connection($host); $this->_resources[] = $host->getResource(); $recv_bytes = 1024; @@ -62,22 +56,22 @@ class App implements ApplicationInterface { set_time_limit(0); ob_implicit_flush(); - $this->_master->set_nonblock(); + $host->set_nonblock(); declare(ticks = 1); - if (false === ($this->_master->bind($address, (int)$port))) { - throw new Exception($this->_master); + if (false === ($host->bind($address, (int)$port))) { + throw new Exception($host); } - if (false === ($this->_master->listen())) { - throw new Exception($this->_master); + if (false === ($host->listen())) { + throw new Exception($host); } do { $changed = $this->_resources; try { - $num_changed = $this->_master->select($changed, $write = null, $except = null, null); + $num_changed = $host->select($changed, $write = null, $except = null, null); } catch (Exception $e) { // master had a problem?...what to do? continue; @@ -85,14 +79,13 @@ class App implements ApplicationInterface { foreach($changed as $resource) { try { - if ($this->_master->getResource() === $resource) { - $conn = $this->_master; - $res = $this->onOpen($conn); - } else { - $conn = $this->_connections[$resource]; - $data = $buf = ''; + $conn = $this->_connections[$resource]; - $bytes = $conn->recv($buf, $recv_bytes, 0); + if ($host->getResource() === $resource) { + $res = $this->onOpen($conn); + } else { + $data = $buf = ''; + $bytes = $conn->getSocket()->recv($buf, $recv_bytes, 0); if ($bytes > 0) { $data = $buf; @@ -134,30 +127,31 @@ class App implements ApplicationInterface { } while (true); } - public function onOpen(SocketInterface $conn) { - $new_connection = clone $conn; - $this->_resources[] = $new_connection->getResource(); - $this->_connections[$new_connection->getResource()] = $new_connection; + public function onOpen(Connection $conn) { + $new_socket = clone $conn->getSocket(); + $new_connection = new Connection($new_socket); + + $this->_resources[] = $new_connection->getSocket()->getResource(); + $this->_connections[$new_connection->getSocket()->getResource()] = $new_connection; return $this->_app->onOpen($new_connection); } - public function onRecv(SocketInterface $from, $msg) { + public function onRecv(Connection $from, $msg) { return $this->_app->onRecv($from, $msg); } - public function onClose(SocketInterface $conn) { - $resource = $conn->getResource(); + public function onClose(Connection $conn) { + $resource = $conn->getSocket()->getResource(); $cmd = $this->_app->onClose($conn); - unset($this->_connections[$resource]); - unset($this->_resources[array_search($resource, $this->_resources)]); + unset($this->_connections[$resource], $this->_resources[array_search($resource, $this->_resources)]); return $cmd; } - public function onError(SocketInterface $conn, \Exception $e) { + public function onError(Connection $conn, \Exception $e) { return $this->_app->onError($conn, $e); } } \ No newline at end of file diff --git a/lib/Ratchet/Application/WebSocket/App.php b/lib/Ratchet/Application/WebSocket/App.php index 4bfb74b..d5fe099 100644 --- a/lib/Ratchet/Application/WebSocket/App.php +++ b/lib/Ratchet/Application/WebSocket/App.php @@ -2,9 +2,9 @@ namespace Ratchet\Application\WebSocket; use Ratchet\Application\WebSocket\Client; use Ratchet\Application\WebSocket\VersionInterface; -use Ratchet\SocketInterface; use Ratchet\Application\ApplicationInterface; use Ratchet\Application\ConfiguratorInterface; +use Ratchet\Resource\Connection; use Ratchet\Resource\Command\Factory; use Ratchet\Resource\Command\CommandInterface; use Ratchet\Resource\Command\Action\SendMessage; @@ -70,11 +70,11 @@ class App implements ApplicationInterface, ConfiguratorInterface { ); } - public function onOpen(SocketInterface $conn) { + public function onOpen(Connection $conn) { $this->_clients[$conn] = new Client; } - public function onRecv(SocketInterface $from, $msg) { + public function onRecv(Connection $from, $msg) { $client = $this->_clients[$from]; if (true !== $client->isHandshakeComplete()) { $response = $client->setVersion($this->getVersion($msg))->doHandshake($msg); @@ -109,7 +109,7 @@ class App implements ApplicationInterface, ConfiguratorInterface { return $this->prepareCommand($cmds); } - public function onClose(SocketInterface $conn) { + public function onClose(Connection $conn) { $cmds = $this->prepareCommand($this->_app->onClose($conn)); // $cmds = new Composite if null @@ -121,7 +121,7 @@ class App implements ApplicationInterface, ConfiguratorInterface { return $cmds; } - public function onError(SocketInterface $conn, \Exception $e) { + public function onError(Connection $conn, \Exception $e) { return $this->_app->onError($conn, $e); } @@ -138,7 +138,7 @@ class App implements ApplicationInterface, ConfiguratorInterface { */ protected function prepareCommand(CommandInterface $command = null) { if ($command instanceof SendMessage) { - $version = $this->_clients[$command->getSocket()]->getVersion(); + $version = $this->_clients[$command->getConnection()]->getVersion(); return $command->setMessage($version->frame($command->getMessage())); } diff --git a/lib/Ratchet/ObserverInterface.php b/lib/Ratchet/ObserverInterface.php index 1b06a46..898e809 100644 --- a/lib/Ratchet/ObserverInterface.php +++ b/lib/Ratchet/ObserverInterface.php @@ -3,10 +3,8 @@ namespace Ratchet; /** * Observable/Observer design pattern interface for handing events on a socket - * @todo Consider an onException method. Since server is running its own loop the app currently doesn't know when a problem is handled * @todo Consider an onDisconnect method for a server-side close()'ing of a connection - onClose would be client side close() - * @todo Consider adding __construct(ObserverInterface $decorator = null) - on Server move Socket as parameter to run() - * @todo Does this belong in \Ratchet\Server\? + * @todo Is this interface needed anymore? */ interface ObserverInterface { /** diff --git a/lib/Ratchet/Resource/Command/Action/CloseConnection.php b/lib/Ratchet/Resource/Command/Action/CloseConnection.php index 847f9e2..aee7f2e 100644 --- a/lib/Ratchet/Resource/Command/Action/CloseConnection.php +++ b/lib/Ratchet/Resource/Command/Action/CloseConnection.php @@ -1,8 +1,8 @@ onClose($this->getSocket()); + $ret = $scope->onClose($this->getConnection()); if ($ret instanceof CommandInterface) { $comp = new Composite; $comp->enqueue($ret); - $rt = new Runtime($this->getSocket()); - $rt->setCommand(function(SocketInterface $socket, ObserverInterface $scope) { - $socket->close(); + $rt = new Runtime($this->getConnection()); + $rt->setCommand(function(Connection $conn, ApplicationInterface $scope) { + $conn->getSocket()->close(); }); $comp->enqueue($rt); return $comp; } - $this->getSocket()->close(); + $this->getConnection()->getSocket()->close(); } } \ No newline at end of file diff --git a/lib/Ratchet/Resource/Command/Action/Runtime.php b/lib/Ratchet/Resource/Command/Action/Runtime.php index 2c65ad4..8dea93e 100644 --- a/lib/Ratchet/Resource/Command/Action/Runtime.php +++ b/lib/Ratchet/Resource/Command/Action/Runtime.php @@ -1,7 +1,7 @@ _command = $callback; } - public function execute(ObserverInterface $scope = null) { - return call_user_func($this->_command, $this->getSocket(), $scope); + public function execute(ApplicationInterface $scope = null) { + return call_user_func($this->_command, $this->getConnection(), $scope); } } \ No newline at end of file diff --git a/lib/Ratchet/Resource/Command/Action/SendMessage.php b/lib/Ratchet/Resource/Command/Action/SendMessage.php index ca94da1..7c65533 100644 --- a/lib/Ratchet/Resource/Command/Action/SendMessage.php +++ b/lib/Ratchet/Resource/Command/Action/SendMessage.php @@ -1,7 +1,8 @@ _message)) { throw new \UnexpectedValueException("Message is empty"); } - $this->getSocket()->write($this->_message, strlen($this->_message)); + $this->getConnection()->getSocket()->write($this->_message, strlen($this->_message)); } } \ No newline at end of file diff --git a/lib/Ratchet/Resource/Command/ActionInterface.php b/lib/Ratchet/Resource/Command/ActionInterface.php index 6110279..514ee24 100644 --- a/lib/Ratchet/Resource/Command/ActionInterface.php +++ b/lib/Ratchet/Resource/Command/ActionInterface.php @@ -1,6 +1,6 @@ _socket = $socket; + public function __construct(Connection $conn) { + $this->_conn = $conn; } - public function getSocket() { - return $this->_socket; + public function getConnection() { + return $this->_conn; } } \ No newline at end of file diff --git a/lib/Ratchet/Resource/Command/CommandInterface.php b/lib/Ratchet/Resource/Command/CommandInterface.php index 16b5251..e0f4992 100644 --- a/lib/Ratchet/Resource/Command/CommandInterface.php +++ b/lib/Ratchet/Resource/Command/CommandInterface.php @@ -1,6 +1,7 @@ setIteratorMode(static::IT_MODE_DELETE); $recursive = new self; diff --git a/lib/Ratchet/Resource/Command/Factory.php b/lib/Ratchet/Resource/Command/Factory.php index ba6b83d..077530a 100644 --- a/lib/Ratchet/Resource/Command/Factory.php +++ b/lib/Ratchet/Resource/Command/Factory.php @@ -1,6 +1,6 @@ _paths as $path) { if (class_exists($path . $name)) { diff --git a/lib/Ratchet/Resource/Connection.php b/lib/Ratchet/Resource/Connection.php new file mode 100644 index 0000000..2cbc7a4 --- /dev/null +++ b/lib/Ratchet/Resource/Connection.php @@ -0,0 +1,62 @@ +_socket = $socket; + } + + /** + * @return int + */ + public function getID() { + return (int)(string)$this->_socket; + } + + /** + * This is here because I couldn't figure out a better/easier way to tie a connection and socket together for the server and commands + * Anyway, if you're here, it's not recommended you use this/directly interact with the socket in your App... + * The command pattern (which is fully flexible, see Runtime) is the safest, desired way to interact with the socket(s). + * @return Ratchet\SocketInterface + */ + public function getSocket() { + return $this->_socket; + } + + /** + * Set an attribute to the connection + * @param mixed + * @param mixed + */ + public function __set($name, $value) { + $this->_data[$name] = $value; + } + + /** + * Get a previously set attribute bound to the connection + * @return mixed + * @throws \InvalidArgumentException + */ + public function __get($name) { + if (!isset($this->_data[$name])) { + throw new \InvalidArgumentException("Attribute '{$name}' not found in Connection {$this->getID()}"); + } + + if (is_callable($this->_data[$name])) { + return $this->_data[$name]($this); + } else { + return $this->_data[$name]; + } + } +} \ No newline at end of file diff --git a/lib/Ratchet/Resource/Connection/Connection.php b/lib/Ratchet/Resource/Connection/Connection.php deleted file mode 100644 index f70a8f1..0000000 --- a/lib/Ratchet/Resource/Connection/Connection.php +++ /dev/null @@ -1,39 +0,0 @@ -_id = (string)$socket->getResource(); - $this->_id = (int)substr($this->_id, strrpos($this->_id, '#') + 1); - } - - /** - * @return int - */ - public function getID() { - return $this->_id; - } - - public function set($name, $val) { - $this->_data[$name] = $val; - } - - public function get($name) { - if (!isset($this->_data[$name])) { - throw new \UnexpectedValueException("Attribute '{$name}' not found in Connection {$this->getID()}"); - } - - return $this->_data[$name]; - } -} \ No newline at end of file diff --git a/lib/Ratchet/Resource/Connection/ConnectionInterface.php b/lib/Ratchet/Resource/Connection/ConnectionInterface.php deleted file mode 100644 index d3e9b08..0000000 --- a/lib/Ratchet/Resource/Connection/ConnectionInterface.php +++ /dev/null @@ -1,30 +0,0 @@ -_resource); } + public function __toString() { + $id = (string)$this->getResource(); + return (string)substr($id, strrpos($id, '#') + 1); + } + /** * @return resource (Socket) */ diff --git a/lib/Ratchet/SocketInterface.php b/lib/Ratchet/SocketInterface.php index 9c33ab0..e165853 100644 --- a/lib/Ratchet/SocketInterface.php +++ b/lib/Ratchet/SocketInterface.php @@ -8,7 +8,7 @@ interface SocketInterface { /** * @return resource */ - public function getResource(); + function getResource(); /** * Send text to the client on the other end of the socket @@ -31,4 +31,9 @@ interface SocketInterface { * Close the open connection to the client/socket */ function close(); + + /** + * Return the unique ID of this socket instance + */ + function __toString(); } \ No newline at end of file diff --git a/tests/Ratchet/Tests/Mock/Application.php b/tests/Ratchet/Tests/Mock/Application.php index 75b7a3b..9515264 100644 --- a/tests/Ratchet/Tests/Mock/Application.php +++ b/tests/Ratchet/Tests/Mock/Application.php @@ -2,21 +2,21 @@ namespace Ratchet\Tests\Mock; use Ratchet\Application\ApplicationInterface; use Ratchet\Tests\Mock\Socket as MockSocket; -use Ratchet\SocketInterface; +use Ratchet\Resource\Connection; class Application implements ApplicationInterface { public function __construct(ApplicationInterface $app = null) { } - public function onOpen(SocketInterface $conn) { + public function onOpen(Connection $conn) { } - public function onRecv(SocketInterface $from, $msg) { + public function onRecv(Connection $from, $msg) { } - public function onClose(SocketInterface $conn) { + public function onClose(Connection $conn) { } - public function onError(SocketInterface $conn, \Exception $e) { + public function onError(Connection $conn, \Exception $e) { } } \ No newline at end of file diff --git a/tests/Ratchet/Tests/Mock/FakeSocket.php b/tests/Ratchet/Tests/Mock/FakeSocket.php index 67c08a2..87a3550 100644 --- a/tests/Ratchet/Tests/Mock/FakeSocket.php +++ b/tests/Ratchet/Tests/Mock/FakeSocket.php @@ -10,6 +10,10 @@ class FakeSocket extends RealSocket { list($this->_arguments['domain'], $this->_arguments['type'], $this->_arguments['protocol']) = static::getConfig($domain, $type, $protocol); } + public function __toString() { + return '1'; + } + public function accept() { } diff --git a/tests/Ratchet/Tests/Resource/ConnectionTest.php b/tests/Ratchet/Tests/Resource/ConnectionTest.php new file mode 100644 index 0000000..bfce598 --- /dev/null +++ b/tests/Ratchet/Tests/Resource/ConnectionTest.php @@ -0,0 +1,32 @@ +_c = new Connection(new FakeSocket); + } + + public function testCanGetWhatIsSet() { + $key = 'hello'; + $val = 'world'; + + $this->_c->{$key} = $val; + $this->assertEquals($val, $this->_c->{$key}); + } + + public function testExceptionThrownOnInvalidGet() { + $this->setExpectedException('InvalidArgumentException'); + $ret = $this->_c->faked; + } + + public function testLambdaReturnValueOnGet() { + $this->markTestIncomplete(); + } +} \ No newline at end of file