From: Sebastian Brix Date: Wed, 14 May 2025 05:35:54 +0000 (+0200) Subject: * Verwende stream_context_set_options statt stream_context_set_option X-Git-Tag: v0.1.4 X-Git-Url: http://git.graph-it.com/?a=commitdiff_plain;h=HEAD;p=graphit%2Fgraph-client.git * Verwende stream_context_set_options statt stream_context_set_option * Psalm-Warnungen bearbeitet --- diff --git a/composer.json b/composer.json index e0bbed6..44acaa3 100644 --- a/composer.json +++ b/composer.json @@ -1,18 +1,21 @@ { - "name": "graphit/graph-client", - "type": "library", - "description": "Graph Client", - "authors": [ - { - "name": "Graph-IT", - "email": "info@graph-it.com" - } - ], - "require": { - "php": ">=5.6.0", - "graphit/graph-common": "^0.1.1" - }, - "autoload": { - "psr-4": { "Graphit\\Graph\\Client\\": "src/" } + "name": "graphit/graph-client", + "type": "library", + "description": "Graph Client", + "authors": [ + { + "name": "Graph-IT", + "email": "info@graph-it.com" } -} + ], + "require": { + "php": ">=8.3.0", + "ext-msgpack": "^2.0.0 | ^3.0.0", + "graphit/graph-common": "^0.1.1" + }, + "autoload": { + "psr-4": { + "Graphit\\Graph\\Client\\": "src/" + } + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 367fcad..9bd45b8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,25 +4,28 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8819b656c8da858aeee741066f00c123", + "content-hash": "3d65b77f5f76008c32a22709c5d58d73", "packages": [ { "name": "graphit/graph-common", - "version": "v0.1.1", + "version": "v0.1.8", "source": { "type": "git", "url": "ssh://git@git.graph-it.com:44022/graphit/graph-common", - "reference": "fae80afcb63233232c0bf4824e599bc101293225" + "reference": "93d05c2c9492c8302d94bf6766db9cb8819467c8" }, "require": { - "php": ">=5.6.0", - "twig/twig": "^1.26.0" + "php": ">=8.3.0", + "twig/twig": "^1.44.7 || ^3.4.3" }, "type": "library", "autoload": { "psr-4": { "Graphit\\Graph\\Common\\": "src/" - } + }, + "files": [ + "bootstrap.php" + ] }, "authors": [ { @@ -31,24 +34,91 @@ } ], "description": "Vom Graphserver und -Client geteilte Sourcen.", - "time": "2022-05-04T16:06:22+00:00" + "time": "2025-04-09T12:47:01+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.25.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -58,12 +128,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -97,7 +164,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -113,40 +180,122 @@ "type": "tidelift" } ], - "time": "2021-10-20T20:35:02+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "twig/twig", - "version": "v1.44.6", + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "ae39480f010ef88adc7938503c9b02d3baf2f3b3" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/ae39480f010ef88adc7938503c9b02d3baf2f3b3", - "reference": "ae39480f010ef88adc7938503c9b02d3baf2f3b3", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "^1.8" + "ext-iconv": "*", + "php": ">=7.2" }, - "require-dev": { - "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.44-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-0": { - "Twig_": "lib/" + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "twig/twig", + "version": "v3.21.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -179,7 +328,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v1.44.6" + "source": "https://github.com/twigphp/Twig/tree/v3.21.1" }, "funding": [ { @@ -191,18 +340,19 @@ "type": "tidelift" } ], - "time": "2021-11-25T13:31:46+00:00" + "time": "2025-05-03T07:21:55+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.6.0" + "php": ">=8.3.0", + "ext-msgpack": "^2.0.0 | ^3.0.0" }, - "platform-dev": [], - "plugin-api-version": "2.2.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/src/Connection.php b/src/Connection.php index 8fd7a90..0a1c348 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -3,53 +3,24 @@ namespace Graphit\Graph\Client; use Graphit\Graph\Common\RemoteConnection; -use Graphit\Graph\Common\ConnectionInterface; +use RuntimeException; +use LogicException; -class Connection extends RemoteConnection implements ConnectionInterface { +/** @api */ +class Connection extends RemoteConnection { - const STREAM_SELECT_TIMEOUT = 2; - - /** @var string */ - private $url; + const int STREAM_SELECT_TIMEOUT = 2; /** @var resource|false */ private $socket = false; - /** @var array */ - private $options; - - /** @var string */ - private $protocoll; - - /** @var int|false */ - private $cryptoType; - - /** @var int */ - private $messageId = 0; - - /** - * @param string $url - */ - public function __construct($url, array $options = []) - { - $this->url = $url; - $this->options = $options; + private int $messageId = 0; - $matches = []; - $pattern = '/^(?[^:]+):\/\//'; - if (preg_match($pattern, $this->url, $matches) !== 1) { - throw new \Exception('Unable to find Transport in URL '.$this->url.'!'); - } - $this->protocoll = $matches['protocoll']; - - $this->cryptoType = false; - $cryptoTypes = [ - 'tls' => STREAM_CRYPTO_METHOD_TLS_CLIENT, - ]; - if (isset($cryptoTypes[$this->protocoll])) { - $this->cryptoType = $cryptoTypes[$this->protocoll]; - } - } + /** @param array{cafile?: string, lofile?: string } $options */ + public function __construct( + private readonly string $url, + private readonly array $options = [] + ) { } public function __destruct() { @@ -58,19 +29,18 @@ class Connection extends RemoteConnection implements ConnectionInterface { } } - /** @return void */ - private function _connect() + private function _connect(): void { $context = stream_context_create(); - if ($this->cryptoType !== false) { - stream_context_set_option($context, [ + if (substr($this->url, 0, 6) === 'tls://') { + stream_context_set_options($context, [ 'ssl' => [ 'verify_peer' => true, 'verify_peer_name' => false, 'ciphers' => 'HIGH', - 'cafile' => $this->options['cafile'], - 'local_cert' => $this->options['lofile'], + 'cafile' => $this->options['cafile'] ?? '', + 'local_cert' => $this->options['lofile'] ?? '', ], ]); } @@ -78,23 +48,19 @@ class Connection extends RemoteConnection implements ConnectionInterface { $timeout = (float)ini_get('default_socket_timeout'); $this->socket = stream_socket_client($this->url, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $context); if ($this->socket === false) { - throw new \Exception("stream_socket_server() failed: $errstr"); + throw new RuntimeException("stream_socket_server() failed: $errstr"); } if ( !stream_set_blocking($this->socket, false)) { - throw new \Exception("stream_set_blocking(false) failed!"); + throw new RuntimeException("stream_set_blocking(false) failed!"); } $this->_readMessage(); } - /** - * @param int $size - * @return string - */ - private function _readBytes($size) { + private function _readBytes(int $size): string { if ( !$this->socket) { - throw new \Exception("Socket not initialized"); + throw new RuntimeException("Socket not initialized"); } $bytes = ''; @@ -103,7 +69,7 @@ class Connection extends RemoteConnection implements ConnectionInterface { $rs = [$this->socket]; $ws = NULL; $es = NULL; - if (@stream_select($rs, $ws, $es, self::STREAM_SELECT_TIMEOUT)) { + if (@stream_select($rs, $ws, $es, self::STREAM_SELECT_TIMEOUT) > 0) { if (feof($this->socket)) { break; } @@ -117,20 +83,31 @@ class Connection extends RemoteConnection implements ConnectionInterface { } if (strlen($bytes) != $size) { - throw new \Exception("Unable to read $size Bytes from the socket"); + throw new RuntimeException("Unable to read $size Bytes from the socket"); } return $bytes; } - /** @return mixed */ - private function _readMessage() { + private function parseUnpackSize(mixed $value): int { + if ( !is_array($value)) + throw new LogicException('array expected'); + + if ( !array_key_exists('size', $value)) + throw new LogicException('key "size" expected'); + + if ( !is_int($value['size'])) + throw new LogicException('int expected'); + + return $value['size']; + } + + private function _readMessage(): mixed { $size = $this->_readBytes(4); - /** @var int $size */ - $size = unpack('Vsize', $size)['size']; + $size = $this->parseUnpackSize(unpack('Vsize', $size)); $message = $this->_readBytes($size); - return msgpack_unpack($message); + return \msgpack_unpack($message); } /** @@ -139,7 +116,7 @@ class Connection extends RemoteConnection implements ConnectionInterface { */ private function _writeBytes($bytes) { if ( !$this->socket) { - throw new \Exception("Socket not initialized"); + throw new RuntimeException("Socket not initialized"); } $size = strlen($bytes); @@ -161,22 +138,18 @@ class Connection extends RemoteConnection implements ConnectionInterface { } if ($written != $size) { - throw new \Exception("Unable to write $size Bytes to socket"); + throw new RuntimeException("Unable to write $size Bytes to socket"); } } - /** - * @param mixed $message - * @return void - */ - private function _writeMessage($message) { + private function _writeMessage(mixed $message): void { /** @var string $message */ $message = msgpack_pack($message); $message = pack('V', strlen($message)).$message; $this->_writeBytes($message); } - /** */ + #[\Override] protected function call($method, array $params) { if ( !$this->socket) { @@ -193,14 +166,14 @@ class Connection extends RemoteConnection implements ConnectionInterface { $response = $this->_readMessage(); if ( !isset($response['jsonrpc']) || $response['jsonrpc'] != $request['jsonrpc']) { - throw new \Exception('Not a JSON-RPC 2.0 response!'); + throw new RuntimeException('Not a JSON-RPC 2.0 response!'); } if (isset($response['error'])) { $error = json_encode($response['error']); - throw new \Exception("JSON-RPC: Remote error: {$error}"); + throw new RuntimeException("JSON-RPC: Remote error: {$error}"); } if ( !isset($response['id']) || $response['id'] != $request['id']) { - throw new \Exception('JSON-RPC id missing or invalid'); + throw new RuntimeException('JSON-RPC id missing or invalid'); } return $response['result'];