From 0c435a30021f04f6225466a115f677c1109e1580 Mon Sep 17 00:00:00 2001 From: Deploy Date: Fri, 20 Dec 2024 13:39:45 +0100 Subject: [PATCH 01/10] chore: added .phpunit.cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7021d1d..0983d73 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .php_cs .php_cs.cache .phpunit.result.cache +.phpunit.cache .vscode clover.xml composer.lock From 1319358961c59912e1bc95e3193bba0aa4c6e3be Mon Sep 17 00:00:00 2001 From: Deploy Date: Fri, 20 Dec 2024 13:40:15 +0100 Subject: [PATCH 02/10] test: improved test --- tests/ExceptionsTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/ExceptionsTest.php b/tests/ExceptionsTest.php index 668aca1..980e2c6 100644 --- a/tests/ExceptionsTest.php +++ b/tests/ExceptionsTest.php @@ -2,6 +2,8 @@ declare(strict_types=1); +use ArangoClient\Exceptions\ArangoException; + uses(Tests\TestCase::class); test('test409 conflict exception', function () { @@ -18,6 +20,7 @@ test('calls to none existing db throw', function () { $this->arangoClient->setDatabase('NoneExistingDb'); - $this->expectExceptionCode(404); $this->schemaManager->hasCollection('dummy'); -}); + + $this->arangoClient->setDatabase($this->testDatabaseName); +})->throws(ArangoException::class); From 84dc9c205a1c41cba564f3ba3202f83934a60262 Mon Sep 17 00:00:00 2001 From: Deploy Date: Fri, 20 Dec 2024 13:40:25 +0100 Subject: [PATCH 03/10] test: removed only. --- tests/SchemaManagerGraphsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/SchemaManagerGraphsTest.php b/tests/SchemaManagerGraphsTest.php index 0559eab..faa8f7d 100644 --- a/tests/SchemaManagerGraphsTest.php +++ b/tests/SchemaManagerGraphsTest.php @@ -345,4 +345,4 @@ expect($result)->toBeTrue(); expect(count($createdGraphs))->toBe(2); expect(count($finalGraphs))->toBe(0); -})->only(); +}); From 3c6c370330b6fb5f105becff4f83b415ee79abfd Mon Sep 17 00:00:00 2001 From: Deploy Date: Fri, 20 Dec 2024 13:47:39 +0100 Subject: [PATCH 04/10] feat: added disconnect functionality --- docs/arangodb-client.md | 32 ++++++++++++++++++-- src/ArangoClient.php | 60 ++++++++++++++++++++++++++++++++++++++ tests/ArangoClientTest.php | 41 ++++++++++++++++++++++---- 3 files changed, 126 insertions(+), 7 deletions(-) diff --git a/docs/arangodb-client.md b/docs/arangodb-client.md index 12cafe6..ffa5a3f 100644 --- a/docs/arangodb-client.md +++ b/docs/arangodb-client.md @@ -66,8 +66,28 @@ Send a request to ArangoDB's HTTP REST API. This is mostly for internal use but $arangoClient->request( 'get', '/_api/version', - 'query' => [ - 'details' => $details + [ + 'query' => [ + 'details' => $details + ] + ] +]); +``` + +### rawRequest(string $method, string $uri, array|HttpRequestOptions $options = []): ResponseInterface|null +Returns the raw response of the request. +*Note* that the request itself is made against the configured endpoint but the databasename is _not_ automatically +prepended to the uri as opposed to a regular request. + + +``` +$arangoClient->rawRequest( + 'get', + '/_api/version', + [ + 'query' => [ + 'details' => $details + ] ] ]); ``` @@ -106,4 +126,12 @@ $arangoClient->schema()->createCollection('users'); Pass chained method to the admin manager. ``` $arangoClient->admin()->getVersion(); +``` + +### disconnect(): bool +Disconnect from the current keep-alive connection, if any. +*Note* that a disconnect request is sent to the database upon destruction of the arangoClient object as well. + +``` +$arangoClient->disconnect(); ``` \ No newline at end of file diff --git a/src/ArangoClient.php b/src/ArangoClient.php index e5d42e7..77c7ded 100644 --- a/src/ArangoClient.php +++ b/src/ArangoClient.php @@ -50,6 +50,42 @@ public function __construct(array $config = [], ?GuzzleClient $httpClient = null $this->httpClient = $httpClient ?? new GuzzleClient($this->config->mapGuzzleHttpClientConfig()); } + public function __destruct() + { + $this->disconnect(); + } + + public function disconnect(): bool + { + $config = $this->getConfig(); + + if ($config['connection'] !== 'Keep-Alive') { + return true; + } + + $response = $this->rawRequest( + 'HEAD', + '/_api/version', + [ + 'headers' => [ + 'Connection' => 'close', + ], + ], + ); + + if ($response === null) { + return false; + } + + $connection = $response->getHeader('Connection'); + if (reset($connection) !== 'Close') { + return false; + } + + return true; + } + + /** * @param array $config */ @@ -58,10 +94,12 @@ public function generateEndpoint(array $config): string if (isset($config['endpoint'])) { return (string) $config['endpoint']; } + $endpoint = 'http://localhost:8529'; if (isset($config['host'])) { $endpoint = (string) $config['host']; } + if (isset($config['port'])) { $endpoint .= ':' . (string) $config['port']; } @@ -96,6 +134,28 @@ public function request(string $method, string $uri, array|HttpRequestOptions $o return new stdClass(); } + /** + * @param array|HttpRequestOptions $options + * + * @throws ArangoException + */ + public function rawRequest(string $method, string $uri, array|HttpRequestOptions $options = []): ResponseInterface|null + { + if (is_array($options)) { + $options = $this->prepareRequestOptions($options); + } + + $response = null; + try { + $response = $this->httpClient->request($method, $uri, $options->all()); + } catch (Throwable $e) { + $this->handleGuzzleException($e); + } + + return $response; + } + + /** * @param array $options * diff --git a/tests/ArangoClientTest.php b/tests/ArangoClientTest.php index 0187280..316d89e 100644 --- a/tests/ArangoClientTest.php +++ b/tests/ArangoClientTest.php @@ -4,14 +4,18 @@ use ArangoClient\Admin\AdminManager; use ArangoClient\ArangoClient; +use ArangoClient\Http\HttpClientConfig; use ArangoClient\Schema\SchemaManager; use ArangoClient\Statement\Statement; use GuzzleHttp\Client; +use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Middleware; use GuzzleHttp\Psr7\Response; +use function PHPUnit\Framework\assertTrue; + uses(Tests\TestCase::class); test('get config', function () { @@ -46,13 +50,13 @@ test('client with host port config', function () { $config = [ 'host' => 'http://127.0.0.1', - 'port' => '1234', + 'port' => '8529', 'username' => 'root', ]; $client = new ArangoClient($config); $retrievedConfig = $client->getConfig(); - expect($retrievedConfig['endpoint'])->toEqual('http://127.0.0.1:1234'); + expect($retrievedConfig['endpoint'])->toEqual('http://127.0.0.1:8529'); }); test('config with alien properties', function () { @@ -60,7 +64,7 @@ 'name' => 'arangodb', 'driver' => 'arangodb', 'host' => 'http://127.0.0.1', - 'port' => '1234', + 'port' => '8529', 'username' => 'root', ]; $client = new ArangoClient($config); @@ -73,8 +77,26 @@ test('set and get http client', function () { $oldClient = $this->arangoClient->getHttpClient(); - $newClient = Mockery::mock(Client::class); + $defaultConfig = [ + 'endpoint' => 'http://localhost:8529', + 'host' => null, + 'port' => null, + 'version' => 1.1, + 'connection' => 'Keep-Alive', + 'allow_redirects' => false, + 'connect_timeout' => 0.0, + 'username' => 'root', + 'password' => null, + 'database' => $this->testDatabaseName, + 'jsonStreamDecoderThreshold' => 1048576, + ]; + + $config = new HttpClientConfig($defaultConfig); + + $newClient = new GuzzleClient($config->mapGuzzleHttpClientConfig()); + $this->arangoClient->setHttpClient($newClient); + $retrievedClient = $this->arangoClient->getHttpClient(); expect($oldClient)->toBeInstanceOf(Client::class); @@ -103,10 +125,13 @@ $database = $this->arangoClient->getDatabase(); expect($database)->toBe($newDatabaseName); + + // Reset DB name + $this->arangoClient->setDatabase($this->testDatabaseName); }); test('database name is used in requests', function () { - $database = 'some_database'; + $database = 'arangodb_php_client__test'; if (!$this->arangoClient->schema()->hasDatabase($database)) { $this->arangoClient->schema()->createDatabase($database); } @@ -234,3 +259,9 @@ $this->schemaManager->deleteCollection($collection); }); + +test('disconnect', function () { + $disconnected = $this->arangoClient->disconnect(); + + assertTrue($disconnected); +}); From 394fd43046287ee5b7d37af4085df7d842f29393 Mon Sep 17 00:00:00 2001 From: Deploy Date: Sat, 21 Dec 2024 18:54:14 +0100 Subject: [PATCH 05/10] chore: simplified disconnect --- src/ArangoClient.php | 48 ++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/ArangoClient.php b/src/ArangoClient.php index 77c7ded..05c1816 100644 --- a/src/ArangoClient.php +++ b/src/ArangoClient.php @@ -43,6 +43,19 @@ class ArangoClient * @throws UnknownProperties */ public function __construct(array $config = [], ?GuzzleClient $httpClient = null) + { + $this->connect($config, $httpClient); + } + + /** + * ArangoClient constructor. + * + * @param array $config + * @param GuzzleClient|null $httpClient + * + * @throws UnknownProperties + */ + public function connect(array $config = [], ?GuzzleClient $httpClient = null): void { $config['endpoint'] = $this->generateEndpoint($config); $this->config = new HttpClientConfig($config); @@ -50,37 +63,14 @@ public function __construct(array $config = [], ?GuzzleClient $httpClient = null $this->httpClient = $httpClient ?? new GuzzleClient($this->config->mapGuzzleHttpClientConfig()); } - public function __destruct() - { - $this->disconnect(); - } - + /** + * We disconnect by creating a new guzzle client. The old client will remove the current connection upon destruction. + * + * @return bool + */ public function disconnect(): bool { - $config = $this->getConfig(); - - if ($config['connection'] !== 'Keep-Alive') { - return true; - } - - $response = $this->rawRequest( - 'HEAD', - '/_api/version', - [ - 'headers' => [ - 'Connection' => 'close', - ], - ], - ); - - if ($response === null) { - return false; - } - - $connection = $response->getHeader('Connection'); - if (reset($connection) !== 'Close') { - return false; - } + $this->httpClient = new GuzzleClient($this->config->mapGuzzleHttpClientConfig()); return true; } From 5f4b793cdc5dbfaf5f3618d1dd006d95d37a1fee Mon Sep 17 00:00:00 2001 From: Deploy Date: Sat, 21 Dec 2024 18:54:34 +0100 Subject: [PATCH 06/10] test: added rawRequest test --- tests/ArangoClientTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/ArangoClientTest.php b/tests/ArangoClientTest.php index 316d89e..6d21337 100644 --- a/tests/ArangoClientTest.php +++ b/tests/ArangoClientTest.php @@ -111,6 +111,14 @@ expect($result->version)->toBeString(); }); + +test('rawRequest', function () { + $response = $this->arangoClient->rawRequest('get', '/_api/version', []); + + expect($response->getStatusCode())->toBe(200); + expect($response->getHeader('Connection')[0])->toBe('Keep-Alive'); +}); + test('get user', function () { $user = $this->arangoClient->getUser(); expect($user)->toBe('root'); From b8a8855852cae2fd17c2a59da1614e075b432322 Mon Sep 17 00:00:00 2001 From: Deploy Date: Sat, 21 Dec 2024 18:54:52 +0100 Subject: [PATCH 07/10] docs: improved disconnect docs --- docs/arangodb-client.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/arangodb-client.md b/docs/arangodb-client.md index ffa5a3f..09d7de0 100644 --- a/docs/arangodb-client.md +++ b/docs/arangodb-client.md @@ -130,7 +130,6 @@ $arangoClient->admin()->getVersion(); ### disconnect(): bool Disconnect from the current keep-alive connection, if any. -*Note* that a disconnect request is sent to the database upon destruction of the arangoClient object as well. ``` $arangoClient->disconnect(); From 49f23460a5a3ef724d0b40e78d9092cb94f0d458 Mon Sep 17 00:00:00 2001 From: Deploy Date: Sat, 21 Dec 2024 19:03:48 +0100 Subject: [PATCH 08/10] test: added connect test --- tests/ArangoClientTest.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/ArangoClientTest.php b/tests/ArangoClientTest.php index 6d21337..ceb76f9 100644 --- a/tests/ArangoClientTest.php +++ b/tests/ArangoClientTest.php @@ -268,6 +268,33 @@ $this->schemaManager->deleteCollection($collection); }); + +test('connect', function () { + $oldHttpClient = $this->arangoClient->getHttpClient(); + $oldHttpClientObjectId = spl_object_id($oldHttpClient); + + $newConfig = [ + 'endpoint' => 'http://localhost:8529', + 'version' => 2, + 'connection' => 'Close', + 'username' => 'root', + 'password' => null, + 'database' => $this->testDatabaseName, + 'jsonStreamDecoderThreshold' => 1048576, + ]; + + $this->arangoClient->connect($newConfig); + + $newHttpClient = $this->arangoClient->getHttpClient(); + $newHttpClientObjectId = spl_object_id($newHttpClient); + + expect($oldHttpClientObjectId)->not()->toBe($newHttpClientObjectId); + + $this->arangoClient->setHttpClient($oldHttpClient); +}); + + + test('disconnect', function () { $disconnected = $this->arangoClient->disconnect(); From 3657e8796b13df2f043f4feff1260d95298968f3 Mon Sep 17 00:00:00 2001 From: Deploy Date: Sat, 21 Dec 2024 19:13:36 +0100 Subject: [PATCH 09/10] fix: connect returns true upon success --- src/ArangoClient.php | 4 +++- tests/ArangoClientTest.php | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ArangoClient.php b/src/ArangoClient.php index 05c1816..c8336ba 100644 --- a/src/ArangoClient.php +++ b/src/ArangoClient.php @@ -55,12 +55,14 @@ public function __construct(array $config = [], ?GuzzleClient $httpClient = null * * @throws UnknownProperties */ - public function connect(array $config = [], ?GuzzleClient $httpClient = null): void + public function connect(array $config = [], ?GuzzleClient $httpClient = null): bool { $config['endpoint'] = $this->generateEndpoint($config); $this->config = new HttpClientConfig($config); $this->httpClient = $httpClient ?? new GuzzleClient($this->config->mapGuzzleHttpClientConfig()); + + return true; } /** diff --git a/tests/ArangoClientTest.php b/tests/ArangoClientTest.php index ceb76f9..05aee95 100644 --- a/tests/ArangoClientTest.php +++ b/tests/ArangoClientTest.php @@ -283,12 +283,13 @@ 'jsonStreamDecoderThreshold' => 1048576, ]; - $this->arangoClient->connect($newConfig); + $response = $this->arangoClient->connect($newConfig); $newHttpClient = $this->arangoClient->getHttpClient(); $newHttpClientObjectId = spl_object_id($newHttpClient); expect($oldHttpClientObjectId)->not()->toBe($newHttpClientObjectId); + expect($response)->toBeTrue(); $this->arangoClient->setHttpClient($oldHttpClient); }); From 34adcb909c58f671e3ecbe6a060d2e30aeb642d9 Mon Sep 17 00:00:00 2001 From: Deploy Date: Sat, 21 Dec 2024 19:13:53 +0100 Subject: [PATCH 10/10] docs: added connect method --- docs/arangodb-client.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/arangodb-client.md b/docs/arangodb-client.md index 09d7de0..c547619 100644 --- a/docs/arangodb-client.md +++ b/docs/arangodb-client.md @@ -128,6 +128,22 @@ Pass chained method to the admin manager. $arangoClient->admin()->getVersion(); ``` +### connect(array $config = [], ?GuzzleClient $httpClient = null): void +You can update the config by calling the connect method. This replaces the underlying connection +and prepares the connection for any requests that follow. + +``` +$config = [ + 'host' => 'http://localhost', + 'port' => '8529', + 'username' => 'your-other-database-username', + 'password' => 'your-other-database-password', + 'database'=> 'your-other-database' +]; + +$arangoClient->connect($config): void +``` + ### disconnect(): bool Disconnect from the current keep-alive connection, if any.