diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index 659c53b..4ffc936 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor/ /.vscode/ composer.lock +phpunit.xml diff --git a/README.md b/README.md old mode 100755 new mode 100644 index e4364df..d91546d --- a/README.md +++ b/README.md @@ -1 +1,100 @@ -web-socket + +# Socket + +## This is a layer for client and server socket connections. + +### PHP extension which have to be installed before to work: +* [PHP 8.0](https://www.php.net/downloads) +* [Yaml](https://www.php.net/manual/en/book.yaml.php) +* [Sockets](https://www.php.net/manual/en/book.sockets.php) + +### Four actions you have to make: +* Configure your configuration file. +* Create client.php or server.php file +* Call methods +* Run from CLI or extends + +### Examples of TCP and UNIX sockets: + +#### 1) Create configuration file. File can be **ONLY** with yaml(yml) or json extensions + +##### * TCP socket configuration using YAML +``` +settings: + socket_type: tcp + address: 127.0.0.1 + port: 8000 + content_length: 2048 +``` + +##### * UNIX socket configuration using YAML +``` +settings: + socket_type: unix + address: socket.sock + content_length: 2048 +``` + +#### 2) Create client and server file handlers. + +##### server.php +``` +runServer(function(ServerSocket $socket) { + echo $socket->send('Hello from server!'); +}); +``` + +##### client.php +``` +runClient(function(ClinetSocket $socket) { + echo $socket->send('Hello from client!'); +}); +``` + +#### 3) Run from CLI +``` + john@doe:/workdir/$ php server.php + john@doe:/workdir/$ php client.php +``` + +#### 4) Extends from QonsilliumSocket +``` +runClient(function(ClientSocket $client) use ($myMessage) { + return $client->send($myMessage); + }); + + if ($serverMessage === 'Hello from server!') { + // handle this action + } + } +} +``` + +But when you will instantiate handler class don't forget to set configuration file with socket settings in constructor method diff --git a/composer.json b/composer.json old mode 100755 new mode 100644 index fd49e93..2d65aa5 --- a/composer.json +++ b/composer.json @@ -1,12 +1,14 @@ { - "name": "rubyqorn/socket", + "name": "qonsillium/socket", "description": "Socket layer over PHP socket api", + "keywords": ["socket", "stream", "datagram", "client", "server", "ipv6", "tcp", "udp", "icmp", "unix", "udg"], + "homepage": "https://github.com/rubyqorn/socket.git", "type": "library", "license": "MIT", "authors": [ { "name": "Anton Hideger", - "email": "rubyqorn@example.com" + "email": "antonhideger1337@gmail.com" } ], "autoload": { @@ -14,7 +16,11 @@ "Qonsillium\\": "src/" } }, - "require": { + "require-dev": { "phpunit/phpunit": "^9.3" + }, + "require": { + "php": "^8.0", + "ext-sockets": "*" } } diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100755 index 3801934..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - ./tests/unit/* - ./tests/unit/SocketTest.php - ./tests/unit/SocketCredentialsTest.php - - \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..0bafd3a --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,8 @@ + + + + + tests/unit + + + \ No newline at end of file diff --git a/src/AbstractSocket.php b/src/AbstractSocket.php new file mode 100644 index 0000000..af3a120 --- /dev/null +++ b/src/AbstractSocket.php @@ -0,0 +1,25 @@ +credentials->getCredential('host'); + } + + /** + * Returns socket read legnth + * @return int + */ + private function getReadLength() + { + return $this->credentials->getCredential('content_length'); + } + + /** + * @return \Qonsillium\Actions\SocketConnector + */ + public function getConnector(string $type) + { + return new SocketConnector($this->getAddress(), $type); + } + + /** + * @return \Qonsillium\Actions\SocketAcceptor + */ + public function getAcceptor(): SocketAcceptor + { + return new SocketAcceptor(); + } + + /** + * @return \Qonsillium\Actions\SocketReader + */ + public function getReader(): SocketReader + { + return new SocketReader($this->getReadLength()); + } + + /** + * @return \Qonsillium\Actions\SocketWriter + */ + public function getWriter(): SocketWriter + { + return new SocketWriter(); + } + + /** + * @return \Qonsillium\Actions\SocketCloser + */ + public function getCloser(): SocketCloser + { + return new SocketCloser(); + } +} diff --git a/src/Actions/AbstractSocketAction.php b/src/Actions/AbstractSocketAction.php new file mode 100644 index 0000000..1a3b665 --- /dev/null +++ b/src/Actions/AbstractSocketAction.php @@ -0,0 +1,45 @@ +make(); + } + + /** + * @param resource $socket + * @return void + */ + public function setSocket($socket) + { + $this->socket = $socket; + } + + /** + * @return resource + */ + public function getSocket() + { + return $this->socket; + } + + /** + * Make socket action + * @return mixed + */ + abstract public function make(); +} diff --git a/src/Actions/Connections/ClientConnection.php b/src/Actions/Connections/ClientConnection.php new file mode 100644 index 0000000..2db7938 --- /dev/null +++ b/src/Actions/Connections/ClientConnection.php @@ -0,0 +1,20 @@ +getAddress(), + $errno, + $errstr, + $this->getTimeout() ?? $this->getTimeout() + ); + } +} diff --git a/src/Actions/Connections/Connection.php b/src/Actions/Connections/Connection.php new file mode 100644 index 0000000..2d33307 --- /dev/null +++ b/src/Actions/Connections/Connection.php @@ -0,0 +1,58 @@ +address = $address; + } + + /** + * @return string + */ + public function getAddress() + { + return $this->address; + } + + /** + * @param int $timeout + * @return void + */ + public function setTimeout(int $timeout) + { + $this->timeout = $timeout; + } + + /** + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Create client or server socket connection + * @return resource + */ + abstract public function create(); +} diff --git a/src/Actions/Connections/ConnectionFactory.php b/src/Actions/Connections/ConnectionFactory.php new file mode 100644 index 0000000..550679a --- /dev/null +++ b/src/Actions/Connections/ConnectionFactory.php @@ -0,0 +1,20 @@ +getAddress(), + $errno, + $errstr + ); + } +} diff --git a/src/Actions/SocketAcceptor.php b/src/Actions/SocketAcceptor.php new file mode 100644 index 0000000..00a5a1f --- /dev/null +++ b/src/Actions/SocketAcceptor.php @@ -0,0 +1,15 @@ +getSocket()); + } +} diff --git a/src/Actions/SocketCloser.php b/src/Actions/SocketCloser.php new file mode 100644 index 0000000..e69a4d3 --- /dev/null +++ b/src/Actions/SocketCloser.php @@ -0,0 +1,15 @@ +getSocket()); + } +} diff --git a/src/Actions/SocketConnector.php b/src/Actions/SocketConnector.php new file mode 100644 index 0000000..fb72e26 --- /dev/null +++ b/src/Actions/SocketConnector.php @@ -0,0 +1,32 @@ +type); + $connection->setAddress($this->address); + return $connection; + } +} diff --git a/src/Actions/SocketReader.php b/src/Actions/SocketReader.php new file mode 100644 index 0000000..3ac5e53 --- /dev/null +++ b/src/Actions/SocketReader.php @@ -0,0 +1,49 @@ +message = $message; + } + + /** + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Read content from connected socket + * @return \Qonsillium\Actions\SocketReader + */ + public function make() + { + $this->setMessage(fread($this->getSocket(), $this->length)); + return $this; + } +} diff --git a/src/Actions/SocketWriter.php b/src/Actions/SocketWriter.php new file mode 100644 index 0000000..44df756 --- /dev/null +++ b/src/Actions/SocketWriter.php @@ -0,0 +1,37 @@ +message = $message; + } + + /** + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Write messages to connected socket + * @return int + */ + public function make() + { + return fwrite($this->getSocket(), $this->getMessage(), strlen($this->getMessage())); + } +} diff --git a/src/Bootstrapper.php b/src/Bootstrapper.php new file mode 100644 index 0000000..208f951 --- /dev/null +++ b/src/Bootstrapper.php @@ -0,0 +1,103 @@ +container = new ServiceContainer(); + $this->provider = new SocketServiceProvider($configFile); + $this->provider->register($this->container); + } + + /** + * @return \Qonsillium\Credential\SocketCredentials + */ + protected function getSocketCredentials(): SocketCredentials + { + return $this->container['credentials.socket'](); + } + + /** + * @return \Qonsillium\Parsers\ConfigParsersFactory + */ + protected function getConfigParsersFactory(): ConfigParsersFactory + { + return $this->container['config.parser'](); + } + + /** + * @return \Qonsillium\Types\TypeConfiguration + */ + protected function getSocketTypeConfigurator(): TypeConfiguration + { + return $this->container['config.socket_type'](); + } + + /** + * @return \Qonsillium\ActionFactory + */ + protected function getSocketActionsFactory(): ActionFactory + { + return $this->container['socket.actions.factory'](); + } + + /** + * @return \Qonsillium\Types\SocketTypeFactory + */ + protected function getSocketTypesFactory(): SocketTypeFactory + { + return $this->container['socket.types.factory'](); + } + + /** + * @return \Qonsillium\SocketFacade + */ + protected function getSocketFacade(): SocketFacade + { + return $this->provider['socket.facade']; + } + + /** + * @return \Qonsillium\ClientSocket + */ + public function getClientSocketConnector(): ClientSocket + { + return $this->container['socket.connection.client'](); + } + + /** + * @return \Qonsillium\ServerSocket + */ + public function getServerSocketConnector(): ServerSocket + { + return $this->container['socket.connection.server'](); + } +} diff --git a/src/Client.php b/src/Client.php deleted file mode 100644 index 8fce42b..0000000 --- a/src/Client.php +++ /dev/null @@ -1,71 +0,0 @@ -createdSocket = $this->connectToSocket(); - - if (!$this->createdSocket) { - return new SocketError('Can\'t accepted connected socket'); - } - - return $this->createdSocket; - } - - /** - * Read message from accepted server socket - * @return \Qonsillium\SocketError|\Qonsillium\SocketMessage - */ - public function read() - { - $socketResponse = new SocketMessage( - $this->readFromSocket($this->createdSocket) - ); - - if (!$socketResponse->getSocketResponse()) { - return new SocketError('Can\'t read message from client socket'); - } - - return $socketResponse; - } - - /** - * Write message to server accepted socket - * @param string $message - * @return int|\Qonsillium\SocketError - */ - public function write(string $message) - { - $writtenMessage = $this->writeToSocket($this->createdSocket, $message); - - if (!$writtenMessage) { - return new SocketError('Can\'t write to accepted client socket'); - } - - return $writtenMessage; - } - - /** - * Call Client destructor method when socket - * connection was broken - * @return void - */ - public function __destruct() - { - return $this->closeSocketConnection($this->createdSocket); - } -} diff --git a/src/ClientSocket.php b/src/ClientSocket.php new file mode 100644 index 0000000..53fdd73 --- /dev/null +++ b/src/ClientSocket.php @@ -0,0 +1,23 @@ +facade->sendFromClient($message); + + if (!$sendedMessage) { + return false; + } + + return $sendedMessage; + } +} diff --git a/src/Credential/ICredential.php b/src/Credential/ICredential.php old mode 100755 new mode 100644 diff --git a/src/Credential/SocketCredentials.php b/src/Credential/SocketCredentials.php old mode 100755 new mode 100644 index ad489a8..7332993 --- a/src/Credential/SocketCredentials.php +++ b/src/Credential/SocketCredentials.php @@ -4,19 +4,6 @@ class SocketCredentials implements ICredential { - /** - * Host name where will - * be connected - * @var string - */ - private string $host; - - /** - * Port for host - * @var string - */ - private string $port; - /** * Set credentials for socket connection * @param string $name @@ -37,4 +24,18 @@ public function getCredential(string $name) { return $this->$name; } + + /** + * Validate existence of credential + * @param string $name + * @return bool + */ + public function validateExistence(string $name) + { + if (!property_exists($this, $name)) { + return false; + } + + return true; + } } diff --git a/src/DI/IServiceProvider.php b/src/DI/IServiceProvider.php new file mode 100644 index 0000000..c4e6e2f --- /dev/null +++ b/src/DI/IServiceProvider.php @@ -0,0 +1,13 @@ +services[$offset] = $value; + } + + /** + * @param string $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->services[$offset]; + } + + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->services[$offset]); + } + + /** + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->services[$offset]); + } + + /** + * @param \Qonsillium\DI\IServiceProvider $provider + * @return void + */ + public function register(IServiceProvider $provider) + { + return $provider->register($this); + } +} diff --git a/src/Exceptions/ConfigFileDoesntExists.php b/src/Exceptions/ConfigFileDoesntExists.php new file mode 100644 index 0000000..fb90560 --- /dev/null +++ b/src/Exceptions/ConfigFileDoesntExists.php @@ -0,0 +1,13 @@ +configFileExtension = pathinfo($this->configFile)['extension']; + $this->nullObj = new NullConfigFile($this->configFile); + } + + /** + * Factory method which return config file + * handler by its extension. Can be ONLY + * yaml(yml) or json + * @return \Qonsillium\Parsers\ConfigParser + */ + public function getParser(): ConfigParser + { + if ($this->configFileExtension === 'yaml' || $this->configFileExtension === 'yml') { + return new YAMLConfigParser($this->configFile); + } elseif ($this->configFileExtension === 'json') { + return new JSONConfigParser($this->configFile); + } else { + return $this->nullObj; + } + } +} diff --git a/src/Parsers/JSONConfigParser.php b/src/Parsers/JSONConfigParser.php new file mode 100644 index 0000000..6096f25 --- /dev/null +++ b/src/Parsers/JSONConfigParser.php @@ -0,0 +1,16 @@ +file), true); + } +} diff --git a/src/Parsers/NullConfigFile.php b/src/Parsers/NullConfigFile.php new file mode 100644 index 0000000..7b72dd8 --- /dev/null +++ b/src/Parsers/NullConfigFile.php @@ -0,0 +1,15 @@ +file); + } +} diff --git a/src/QonsilliumSocket.php b/src/QonsilliumSocket.php new file mode 100644 index 0000000..10aff1c --- /dev/null +++ b/src/QonsilliumSocket.php @@ -0,0 +1,85 @@ +bootstrapper = new Bootstrapper($configFile); + } + + /** + * Return ClientSocket handler + * @return \Qonsillium\ClientSocket + */ + protected function getClientSocket(): ClientSocket + { + return $this->bootstrapper->getClientSocketConnector(); + } + + /** + * Return ServerSocket handler + * @return \Qonsillium\ServerSocket + */ + protected function getServerSocket(): ServerSocket + { + return $this->bootstrapper->getServerSocketConnector(); + } + + /** + * Run client socket and handle user + * setted callback function. + * @param \Closure $handler + * + * Example: + * $socket = QonsilliumSocket('config.yml'); + * $socket->runClient(function(ClientSocket $client) { + * $client->send('Hello from client'); + * }) + * + * @return void + */ + public function runClient(Closure $handler) + { + return $handler($this->getClientSocket()); + } + + /** + * Run server socket and handle user + * setted callback function. + * @param \Closure $handler + * + * Example: + * $socket = QonsilliumSocket('config.yml'); + * $socket->runClient(function(ServerSocket $server) { + * $server->send('Hello from server'); + * }) + * + * @return void + */ + public function runServer(Closure $handler) + { + return $handler($this->getServerSocket()); + } +} diff --git a/src/Server.php b/src/Server.php deleted file mode 100644 index 549addb..0000000 --- a/src/Server.php +++ /dev/null @@ -1,71 +0,0 @@ -createdSocket = $this->acceptConnectionOnSocket(); - - if (!$this->createdSocket) { - return new SocketError('Can\'t accepted connected socket'); - } - - return $this->createdSocket; - } - - /** - * Read message from accepted client socket - * @return \Qonsillium\SocketError|\Qonsillium\SocketMessage - */ - public function read() - { - $socketResponse = new SocketMessage( - $this->readFromSocket($this->createdSocket) - ); - - if (!$socketResponse->getSocketResponse()) { - return new SocketError('Can\'t read message from client socket'); - } - - return $socketResponse; - } - - /** - * Write message to client accepted socket - * @param string $message - * @return int|\Qonsillium\SocketError - */ - public function write(string $message) - { - $writtenMessage = $this->writeToSocket($this->createdSocket, $message); - - if (!$writtenMessage) { - return new SocketError('Can\'t write to accepted client socket'); - } - - return $writtenMessage; - } - - /** - * Call Server destructor method when socket - * connection was broken - * @return void - */ - public function __destruct() - { - return $this->closeSocketConnection($this->createdSocket); - } -} diff --git a/src/ServerSocket.php b/src/ServerSocket.php new file mode 100644 index 0000000..2814ed0 --- /dev/null +++ b/src/ServerSocket.php @@ -0,0 +1,23 @@ +facade->sendFromServer($message); + + if (!$sendedMessage) { + return false; + } + + return $sendedMessage; + } +} diff --git a/src/ServiceContainer.php b/src/ServiceContainer.php deleted file mode 100644 index 2eb4e41..0000000 --- a/src/ServiceContainer.php +++ /dev/null @@ -1,123 +0,0 @@ -helper = new ServicesHelper(); - } - - /** - * Validate service name existence in container - * @param string $serviceName - * @return string - * @throws \Qonsillium\Exceptions\ServiceNotFound - */ - private function validateServiceExistence(string $serviceName) - { - return $this->helper->validateService($serviceName); - } - - /** - * Validate availablility of Server socket layer - * and return instance of this class - * @return \Qonsillium\Server - */ - public function serverSocketHandler(): Server - { - $service = $this->validateServiceExistence('server_socket'); - - if (!$service) { - return false; - } - - return new $service(); - } - - /** - * Validate availablility of Client socket layer - * and return instance of this class - * @return \Qonsillium\Client - */ - public function clientSocketHandler(): Client - { - $service = $this->validateServiceExistence('client_socket'); - - if (!$service) { - return false; - } - - return new $service(); - } - - /** - * Validate availablility of Socket layer - * and return instance of this class - * @return \Qonsillium\Socket - */ - public function socketLayerHandler(): Socket - { - $service = $this->validateServiceExistence('socket_layer'); - - if (!$service) { - return false; - } - - return new $service(); - } - - /** - * Validate availablility of SocketEndpoint layer - * and return instance of this class - * @return \Qonsillium\SocketEndpoint - */ - public function socketsManagerHandler(): SocketEndpoint - { - $service = $this->validateServiceExistence('socket_endpoint'); - - if (!$service) { - return false; - } - - return new $service(); - } - - /** - * Validate availablility of SocketCredentials layer - * and return instance of this class - * @return \Qonsillium\SocketCredentials - */ - public function socketCredentialsHandler(): SocketCredentials - { - $service = $this->validateServiceExistence('socket_credential'); - - if (!$service) { - return false; - } - - return new $service(); - } - - public function bootstrap() - { - // - } -} diff --git a/src/ServicesHelper.php b/src/ServicesHelper.php deleted file mode 100644 index 40d0536..0000000 --- a/src/ServicesHelper.php +++ /dev/null @@ -1,81 +0,0 @@ - Client::class, - 'server_socket' => Server::class, - 'socket_layer' => Socket::class, - 'socket_endpoint' => SocketEndpoint::class, - 'socket_credentials' => SocketCredentials::class - ]; - - /** - * Register new service in service container. Must set - * new service using little descriptive name, but if - * you SET service name which already exists this will - * delete previous service, and class name which respond for - * this service - * @param string $descriptiveName - * @param string $className - * @return void - */ - public function addToList(string $descriptiveName, string $className) - { - $this->container[$descriptiveName] = $className; - } - - /** - * Return service from services list by unique - * key - * @param string $serviceName - * @return mixed - */ - public function getFromList(string $serviceName) - { - return $this->container[$serviceName]; - } - - /** - * Validate service existence and return namespace - * of this service in string representation or throws - * exception - * @param string $serviceName - * @return string - * @throws \Qonsillium\Exceptions\ServiceNotFound - */ - public function validateService(string $serviceName) - { - $service = "\\{$this->getFromList($serviceName)}"; - - if (!class_exists($service)) { - throw new ServiceNotFound("Service with {$serviceName} was not found"); - } - - return $service; - } - - /** - * Remove service from services list by unique key - * @param string $serviceName - * @return void - */ - public function removeFromList(string $serviceName) - { - unset($this->container[$serviceName]); - } -} diff --git a/src/Socket.php b/src/Socket.php deleted file mode 100755 index 4026462..0000000 --- a/src/Socket.php +++ /dev/null @@ -1,168 +0,0 @@ -host = $host; - $this->port = $port; - $this->socket = $this->create(); - } - - /** - * Creates and returns a socket resource, also - * referred to as an endpoint of communication - * @return resource - */ - public function create() - { - return socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - } - - /** - * Binds a name to a socket - * @return bool - */ - public function bind() - { - return socket_bind($this->socket, $this->host, $this->port); - } - - /** - * Listens for a connection on a socket - * @return bool - */ - public function listen() - { - return socket_listen($this->socket, 1); - } - - /** - * Accept a connection on a socket - * @return resource - */ - public function accept() - { - return socket_accept($this->socket); - } - - /** - * Bind, listen and return accepted socket - * connection - * @return resource - */ - public function acceptSocketConnection() - { - if (!$this->bind()) { - return false; - } - - if (!$this->listen()) { - return false; - } - - $acceptedSocket = $this->accept(); - - if (!$acceptedSocket) { - return false; - } - - return $acceptedSocket; - } - - /** - * Return resource of type Socket - * which was created right now. Can get - * access by connect method - * @return resource - */ - public function getConnectedSocket() - { - return $this->socket; - } - - /** - * Initiates a connection on a socket - * @return \Qonsillium\Socket - */ - public function connect() - { - socket_connect( - $this->socket, $this->host, $this->port - ); - - return $this->socket; - } - - /** - * Write to a socket specified messages - * @param string $message - * @return int - */ - public function write($socket, string $message) - { - return socket_write( - $socket, $message, strlen($message) - ); - } - - /** - * Reads a maximum of length bytes from a socket - * @param resource $socket - * @return string - */ - public function read($socket) - { - while(socket_recv($socket, $buffer, 2048, 0)) { - $this->content .= $buffer; - } - - return $this->content; - } - - /** - * Close specified socket connection - * @param resource $socket - * @return void - */ - public function close($socket) - { - return socket_close($socket); - } -} diff --git a/src/SocketEndpoint.php b/src/SocketEndpoint.php deleted file mode 100755 index 7e00777..0000000 --- a/src/SocketEndpoint.php +++ /dev/null @@ -1,95 +0,0 @@ -credential = $credential; - } - - /** - * Return instance of SocketCredentials - * @return SocketCredentials - */ - public function getCredentials() - { - return $this->credential; - } - - /** - * Socket factory method which manipulate - * PHP socket API - * @return \Qonsillium\Socket - */ - protected function getSocket() - { - return new Socket( - $this->getCredentials()->getCredential('host'), - $this->getCredentials()->getCredential('port') - ); - } - - /** - * Bind, listen and accept connection on socket. - * Usually use when wanted to create server socket - * @return resource|bool - */ - protected function acceptConnectionOnSocket() - { - return $this->getSocket()->acceptSocketConnection(); - } - - /** - * Connect to created to socket. Usually use - * when wanted to create client socket - * @return resource|bool - */ - protected function connectToSocket() - { - return $this->getSocket()->connect(); - } - - /** - * Write content from current listening socket - * @param resource $socket - * @param string $message - * @return int - */ - protected function writeToSocket($socket, string $message) - { - return $this->getSocket()->write($socket, $message); - } - - /** - * Read content from current listening socket - * @param resource $socket - * @return string - */ - protected function readFromSocket($socket) - { - return $this->getSocket()->read($socket); - } - - /** - * Close socket connection which was created - * acceptSocketConnection() or connectToSocket() - * @param resource $socket - * @return void - */ - protected function closeSocketConnection($socket) - { - return $this->getSocket()->close($socket); - } -} diff --git a/src/SocketError.php b/src/SocketError.php deleted file mode 100644 index 1ed0e61..0000000 --- a/src/SocketError.php +++ /dev/null @@ -1,31 +0,0 @@ -errorMessage = $error; - } - - /** - * Return setted socket error - * @return string - */ - public function getSocketError() - { - return $this->errorMessage; - } -} diff --git a/src/SocketFacade.php b/src/SocketFacade.php new file mode 100644 index 0000000..98014db --- /dev/null +++ b/src/SocketFacade.php @@ -0,0 +1,173 @@ +factory->getConnector($connectionType); + return $connector()->create(); + } + + /** + * Accept socket connections + * @param \Socket $socket + * @return bool + */ + protected function acceptSocket($socket) + { + $acceptor = $this->factory->getAcceptor(); + $acceptor->setSocket($socket); + $acceptAction = $acceptor(); + + if (!$acceptAction) { + return false; + } + + return $acceptAction; + } + + /** + * Read messages from client or server sockets + * @param \Socket $socket + * @return bool|\Qonsillium\SocketReader + */ + protected function readSocket($socket) + { + $reader = $this->factory->getReader(); + $reader->setSocket($socket); + $readAction = $reader(); + + if (!$readAction) { + return false; + } + + return $readAction; + } + + /** + * Write messages on client or server sockets + * @param \Socket $socket + * @param string $message + * @return bool|int + */ + protected function writeSocket($socket, string $message) + { + $writer = $this->factory->getWriter(); + $writer->setSocket($socket); + $writer->setMessage($message); + $writeAction = $writer(); + + if (!$writeAction) { + return false; + } + + return $writeAction; + } + + /** + * Close accepted or created socket connections + * @throws \Qonsillium\Exceptions\FailedCloseSocket + * @return void + */ + public function closeSocket($socket) + { + $closer = $this->factory->getCloser(); + $closer->setSocket($socket); + return $closer(); + } + + /** + * This method can be used only with ServerSocket. + * Here we create, bind, listen, accept, read and + * write socket + * @param string $message + * @throws \Qonsillium\Exceptions\FailedCconnectSocket + * @throws \Qonsillium\Exceptions\FailedWriteSocket + * @return string + */ + public function sendFromServer(string $message) + { + $server = $this->connectSocket('server'); + + if (!$server) { + throw new FailedConnectSocket('Failed to connect server socket'); + } + + while ($client = $this->acceptSocket($server)) { + $written = $this->writeSocket($client, $message); + + if (!$written) { + throw new FailedWriteSocket('Failed to write to client socket'); + } + + $this->content .= $this->readSocket($client)->getMessage(); + $this->closeSocket($client); + } + + $this->closeSocket($server); + + return $this->content; + } + + /** + * This method can be used only with ClientSocket. + * Here we create, read and write socket + * @param string $message + * @throws \Qonsillium\Exceptions\FailedConnectSocket + * @throws \Qonsillium\Exceptions\FailedWriteSocket + * @throws \Qonsillium\Exceptions\FailedReadSocket + * @return string + */ + public function sendFromClient(string $message) + { + $client = $this->connectSocket('client'); + + if (!$client) { + throw new FailedConnectSocket('Failed to connect client socket'); + } + + $written = $this->writeSocket($client, $message); + + if (!$written) { + throw new FailedWriteSocket('Failed to write to server socket'); + } + + while(!feof($client)) { + $this->content .= $this->readSocket($client)->getMessage(); + } + + $this->closeSocket($client); + + return $this->content; + } +} diff --git a/src/SocketMessage.php b/src/SocketMessage.php deleted file mode 100755 index 9af76f7..0000000 --- a/src/SocketMessage.php +++ /dev/null @@ -1,31 +0,0 @@ -message = $message; - } - - /** - * Get current readed socket response - * @return string - */ - public function getSocketResponse() - { - return $this->message; - } -} diff --git a/src/SocketServiceProvider.php b/src/SocketServiceProvider.php new file mode 100644 index 0000000..13f4d48 --- /dev/null +++ b/src/SocketServiceProvider.php @@ -0,0 +1,93 @@ +configFile = $configFile; + } + + /** + * Register socket services + * @param \Qonsillium\DI\ServiceContainer + */ + public function register(ServiceContainer $container) + { + $container['config.file'] = $this->configFile; + + $container['config.parser'] = function() use ($container) { + return new ConfigParsersFactory($container['config.file']); + }; + + $container['config.socket_type'] = function() use ($container) { + return new TypeConfiguration($container['config.parser']()->getParser()->parse()); + }; + + $container['credentials.socket'] = function() use ($container) { + $credential = new SocketCredentials(); + $parsedConfig = $container['config.parser']()->getParser()->parse()['settings']; + + // Set socket host credentials. We have only two types + // It's unix and tcp, another will be ignored + if ($parsedConfig['socket_type'] === 'unix') { + $credential->setCredential( + 'host', + "{$parsedConfig['socket_type']}://{$parsedConfig['address']}" + ); + } elseif($parsedConfig['socket_type'] === 'tcp') { + $credential->setCredential( + 'host', + "{$parsedConfig['socket_type']}://{$parsedConfig['address']}:{$parsedConfig['port']}" + ); + } + + $credential->setCredential( + 'content_length', + $parsedConfig['content_length'] + ); + + return $credential; + }; + + $container['socket.actions.factory'] = function() use ($container) { + return new ActionFactory($container['credentials.socket']()); + }; + + $container['socket.types.factory'] = function() use ($container) { + return new SocketTypeFactory($container['config.socket_type']()); + }; + + $container['socket.facade'] = function() use ($container) { + return new SocketFacade($container['socket.actions.factory']()); + }; + + $container['socket.connection.client'] = function() use ($container) { + return new ClientSocket($container['socket.facade']()); + }; + + $container['socket.connection.server'] = function() use ($container) { + return new ServerSocket($container['socket.facade']()); + }; + } +} diff --git a/src/Types/NullSocketType.php b/src/Types/NullSocketType.php new file mode 100644 index 0000000..9f585a4 --- /dev/null +++ b/src/Types/NullSocketType.php @@ -0,0 +1,15 @@ +configuration->validateSettingsExistence($setting)) { + return false; + } + + return true; + } + + /** + * Return setting which was setted in + * configuration file and validate existence + * @return bool|string + */ + public function get(string $setting) + { + if (!$this->validate($setting)) { + return false; + } + + return $this->configuration->getConfigurationSetting($setting); + } + + /** + * Configure TCP or Unix socket + * @return void + */ + abstract public function configure(); +} diff --git a/src/Types/SocketTypeFactory.php b/src/Types/SocketTypeFactory.php new file mode 100644 index 0000000..d33db8f --- /dev/null +++ b/src/Types/SocketTypeFactory.php @@ -0,0 +1,42 @@ +nullObj = new NullSocketType($this->configuration); + } + + /** + * Factory method which return socket by + * type + * @param string $type + * @return \Qonsillium\Types\SocketType + */ + public function getSocket(string $type): SocketType + { + if ($type === 'tcp') { + return new TCPSocketType($this->configuration); + } elseif ($type === 'unix') { + return new UnixSocketType($this->configuration); + } else { + return $this->nullObj; + } + } +} diff --git a/src/Types/TCPSocketType.php b/src/Types/TCPSocketType.php new file mode 100644 index 0000000..0349e02 --- /dev/null +++ b/src/Types/TCPSocketType.php @@ -0,0 +1,15 @@ +configuration->configure(); + } +} diff --git a/src/Types/TypeConfiguration.php b/src/Types/TypeConfiguration.php new file mode 100644 index 0000000..484a396 --- /dev/null +++ b/src/Types/TypeConfiguration.php @@ -0,0 +1,58 @@ +settings as $key => $value) { + $this->configured[$key] = $value; + } + } + + /** + * Validate settings existence in list + * @param string $setting + * @return bool + */ + public function validateSettingsExistence(string $setting) + { + if (!isset($this->configured[$setting])) { + return false; + } + + return true; + } + + /** + * Return configured setting by passsed name + * @return string + */ + public function getConfigurationSetting(string $setting) + { + return $this->configured[$setting]; + } +} diff --git a/src/Types/UnixSocketType.php b/src/Types/UnixSocketType.php new file mode 100644 index 0000000..eb6f99a --- /dev/null +++ b/src/Types/UnixSocketType.php @@ -0,0 +1,15 @@ +configuration->configure(); + } +} diff --git a/tests/unit/ActionFactoryTest.php b/tests/unit/ActionFactoryTest.php new file mode 100644 index 0000000..39a79da --- /dev/null +++ b/tests/unit/ActionFactoryTest.php @@ -0,0 +1,91 @@ +credentials = new SocketCredentials; + $this->credentials->setCredential('host', 'tcp://127.0.0.1:8000'); + $this->credentials->setCredential('content_length', 2048); + + $this->factory = new ActionFactory($this->credentials); + } + + public function testGetAddressReturnsSameStringAndIsString() + { + $this->assertIsString( + $this->factory->getAddress(), + 'ActionFactory::getAddress is null. Expected string' + ); + + $this->assertSame( + 'tcp://127.0.0.1:8000', + $this->factory->getAddress(), + 'ActionFactory::getAddress expected string with "tcp://127.0.0.1:8000", but getting null' + ); + } + + public function testGetConnectorReturnsSocketConnectorInstance() + { + $this->assertInstanceOf( + SocketConnector::class, + $this->factory->getConnector('server'), + 'ActionFactory::getConnector expected type SocketConnector but getting null' + ); + } + + public function testGetAcceptorReturnsSocketAcceptorInstance() + { + $this->assertInstanceOf( + SocketAcceptor::class, + $this->factory->getAcceptor(), + 'ActionFactory::getAcceptor expected type SocketAcceptor but getting null' + ); + } + + public function testGetReaderReturnsSocketReaderInstance() + { + $this->assertInstanceOf( + SocketReader::class, + $this->factory->getReader(), + 'ActionFactory::getReader expected type SocketReader but getting null' + ); + } + + public function testGetReaderReturnsSocketWriterInstance() + { + $this->assertInstanceOf( + SocketWriter::class, + $this->factory->getWriter(), + 'ActionFactory::getWriter expected type SocketWriter but getting null' + ); + } + + public function testGetReaderReturnsSocketCloserInstance() + { + $this->assertInstanceOf( + SocketCloser::class, + $this->factory->getCloser(), + 'ActionFactory::getWriter expected type SocketCloser but getting null' + ); + } + + public function tearDown(): void + { + unset($this->credentials); + unset($this->factory); + } +} diff --git a/tests/unit/Actions/Connections/ConnectionFactoryTest.php b/tests/unit/Actions/Connections/ConnectionFactoryTest.php new file mode 100644 index 0000000..cad6584 --- /dev/null +++ b/tests/unit/Actions/Connections/ConnectionFactoryTest.php @@ -0,0 +1,15 @@ +assertInstanceOf(Connection::class, $factory->getConnection('server')); + } +} diff --git a/tests/unit/Credential/SocketCredentialTest.php b/tests/unit/Credential/SocketCredentialTest.php new file mode 100644 index 0000000..a389a3e --- /dev/null +++ b/tests/unit/Credential/SocketCredentialTest.php @@ -0,0 +1,25 @@ +setCredential('host', 'tcp://127.0.0.1:8000'); + + $this->assertSame('tcp://127.0.0.1:8000', $credentials->getCredential('host')); + } + + public function testPropertyExistenceValidatorReturnsBool() + { + $credentials = new SocketCredentials(); + $credentials->setCredential('host', 'tcp://127.0.0.1:8000'); + $this->assertTrue( + $credentials->validateExistence('host'), + "SocketCredentials::validateExistence. Expected property 'host', but doesn't exists" + ); + } +} diff --git a/tests/unit/Parsers/ConfigParsersFactoryTest.php b/tests/unit/Parsers/ConfigParsersFactoryTest.php new file mode 100644 index 0000000..9988322 --- /dev/null +++ b/tests/unit/Parsers/ConfigParsersFactoryTest.php @@ -0,0 +1,22 @@ +assertInstanceOf( + ConfigParser::class, + $factory->getParser(), + 'ConfigParserFactory::getParser, Expected type ConfigParser, but getting null' + ); + } +} diff --git a/tests/unit/Parsers/JSONConfigParserTest.php b/tests/unit/Parsers/JSONConfigParserTest.php new file mode 100644 index 0000000..44e5423 --- /dev/null +++ b/tests/unit/Parsers/JSONConfigParserTest.php @@ -0,0 +1,16 @@ +assertIsArray( + $factory->getParser()->parse(), + "JSONConfigParser::parse. Expected type 'array', but getting null" + ); + } +} diff --git a/tests/unit/Parsers/YAMLConfigParserTest.php b/tests/unit/Parsers/YAMLConfigParserTest.php new file mode 100644 index 0000000..85015c8 --- /dev/null +++ b/tests/unit/Parsers/YAMLConfigParserTest.php @@ -0,0 +1,16 @@ +assertIsArray( + $factory->getParser()->parse(), + "YAMLConfigParser::parse. Expected 'array', but getting null" + ); + } +} diff --git a/tests/unit/SocketCredentialsTest.php b/tests/unit/SocketCredentialsTest.php deleted file mode 100755 index ec2c63d..0000000 --- a/tests/unit/SocketCredentialsTest.php +++ /dev/null @@ -1,24 +0,0 @@ -assertInstanceOf( - \WebSocket\Credential\ICredential::class, - $credential - ); - - $credential->setCredential('host', 'localhost'); - - $this->assertSame( - $credential->getCredential('host'), - 'localhost' - ); - } -} \ No newline at end of file diff --git a/tests/unit/SocketServiceProviderTest.php b/tests/unit/SocketServiceProviderTest.php new file mode 100644 index 0000000..c03a9ba --- /dev/null +++ b/tests/unit/SocketServiceProviderTest.php @@ -0,0 +1,23 @@ +register($container); + + $this->assertSame( + $configFilePath, + $container['config.file'], + "SocketServiceProvider::register expected {$configFilePath} file, but got wrong" + ); + } +} diff --git a/tests/unit/SocketTest.php b/tests/unit/SocketTest.php deleted file mode 100755 index 65a3b85..0000000 --- a/tests/unit/SocketTest.php +++ /dev/null @@ -1,19 +0,0 @@ -setCredentials(new SocketCredentials()); - - $this->assertInstanceOf( - \WebSocket\Credential\SocketCredentials::class, - $socket->getCredentials() - ); - } -} diff --git a/tests/unit/Types/SocketTypeFactoryTest.php b/tests/unit/Types/SocketTypeFactoryTest.php new file mode 100644 index 0000000..010ea9f --- /dev/null +++ b/tests/unit/Types/SocketTypeFactoryTest.php @@ -0,0 +1,25 @@ +getParser()->parse()['settings']); + $socketTypeFactory = new SocketTypeFactory($configuration); + + // SocketTypeFactory::getSocket will always return SocketType instance + // that's because we have a null object which contain realize interface of + // SocketType. Even if we will pass wrong file will be returned null object + $this->assertInstanceOf( + SocketType::class, + $socketTypeFactory->getSocket('tcp') + ); + } +} diff --git a/tests/unit/Types/TypeConfigurationTest.php b/tests/unit/Types/TypeConfigurationTest.php new file mode 100644 index 0000000..4a42172 --- /dev/null +++ b/tests/unit/Types/TypeConfigurationTest.php @@ -0,0 +1,33 @@ +getParser()->parse()['settings']); + $configuration->configure(); + + $this->assertTrue( + $configuration->validateSettingsExistence('address'), + "TypeConfiguration::validateSettingExistence. Setting 'host' doesn't exists in settings list" + ); + } + + public function testGetConfigurationSettingReturnsSameString() + { + $factorySettings = new ConfigParsersFactory(dirname(__DIR__, 1) . '/fixtures/config_tcp.yaml'); + $configuration = new TypeConfiguration($factorySettings->getParser()->parse()['settings']); + $configuration->configure(); + + $this->assertSame( + '127.0.0.1', + $configuration->getConfigurationSetting('address'), + "TypeConfiguration::getConfigurationSetting. Expected '127.0.0.1', but got not the same string" + ); + } +} diff --git a/tests/unit/fixtures/config_tcp.json b/tests/unit/fixtures/config_tcp.json new file mode 100644 index 0000000..152c6ea --- /dev/null +++ b/tests/unit/fixtures/config_tcp.json @@ -0,0 +1,8 @@ +{ + "settings": { + "socket_type": "tcp", + "content_length": "2048", + "address": "127.0.0.1", + "port": "8001" + } +} \ No newline at end of file diff --git a/tests/unit/fixtures/config_tcp.yaml b/tests/unit/fixtures/config_tcp.yaml new file mode 100644 index 0000000..5f103e6 --- /dev/null +++ b/tests/unit/fixtures/config_tcp.yaml @@ -0,0 +1,5 @@ +settings: + socket_type: tcp + read_length: 2048 + address: 127.0.0.1 + port: 8000 \ No newline at end of file diff --git a/tests/unit/fixtures/config_unix.json b/tests/unit/fixtures/config_unix.json new file mode 100644 index 0000000..a643793 --- /dev/null +++ b/tests/unit/fixtures/config_unix.json @@ -0,0 +1,7 @@ +{ + "settings": { + "socket_type": "tcp", + "content_length": "2048", + "address": "socket.sock" + } +} \ No newline at end of file diff --git a/tests/unit/fixtures/config_unix.yaml b/tests/unit/fixtures/config_unix.yaml new file mode 100644 index 0000000..e0b4dc4 --- /dev/null +++ b/tests/unit/fixtures/config_unix.yaml @@ -0,0 +1,4 @@ +settings: + socket_type: tcp + read_length: 2048 + address: socket.sock \ No newline at end of file