Skip to content

Commit dfa8edc

Browse files
committed
Implemented the complete GetServerInfo request flow across the Neo4j PHP Client and Testkit backend. This includes full request/response handling, driver integration, and a critical fix for routing table validation.
1 parent cb420ad commit dfa8edc

File tree

18 files changed

+180
-257
lines changed

18 files changed

+180
-257
lines changed

src/Basic/Driver.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Laudis\Neo4j\Contracts\AuthenticateInterface;
1717
use Laudis\Neo4j\Contracts\DriverInterface;
1818
use Laudis\Neo4j\Databags\DriverConfiguration;
19+
use Laudis\Neo4j\Databags\ServerInfo;
1920
use Laudis\Neo4j\Databags\SessionConfiguration;
2021
use Laudis\Neo4j\DriverFactory;
2122
use Laudis\Neo4j\Formatter\SummarizedResultFormatter;
@@ -44,6 +45,11 @@ public function verifyConnectivity(?SessionConfiguration $config = null): bool
4445
return $this->driver->verifyConnectivity($config);
4546
}
4647

48+
public function getServerInfo(?SessionConfiguration $config = null): ServerInfo
49+
{
50+
return $this->driver->getServerInfo($config);
51+
}
52+
4753
public static function create(string|UriInterface $uri, ?DriverConfiguration $configuration = null, ?AuthenticateInterface $authenticate = null): self
4854
{
4955
$driver = DriverFactory::create($uri, $configuration, $authenticate, SummarizedResultFormatter::create());

src/Bolt/BoltConnection.php

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,6 @@ public function reset(): void
204204
$this->subscribedResults = [];
205205
}
206206

207-
private function prepareForBegin(): void
208-
{
209-
if (in_array($this->getServerState(), ['STREAMING', 'TX_STREAMING'], true)) {
210-
$this->discardUnconsumedResults();
211-
}
212-
}
213-
214207
/**
215208
* Begins a transaction.
216209
*
@@ -332,22 +325,17 @@ public function __destruct()
332325

333326
public function close(): void
334327
{
335-
try {
336-
if ($this->isOpen()) {
337-
if ($this->isStreaming() && (($this->connectionUsed['reader'] ?? false) || ($this->connectionUsed['writer'] ?? false))) {
338-
$this->discardUnconsumedResults();
339-
}
340-
341-
// Only send GOODBYE if the connection was ever used
342-
if (($this->connectionUsed['reader'] ?? false) || ($this->connectionUsed['writer'] ?? false)) {
343-
$message = $this->messageFactory->createGoodbyeMessage();
344-
$message->send();
345-
}
346-
347-
unset($this->boltProtocol);
328+
if ($this->isOpen()) {
329+
if ($this->isStreaming() && (($this->connectionUsed['reader'] ?? false) || ($this->connectionUsed['writer'] ?? false))) {
330+
$this->discardUnconsumedResults();
331+
}
332+
333+
if (($this->connectionUsed['reader'] ?? false) || ($this->connectionUsed['writer'] ?? false)) {
334+
$message = $this->messageFactory->createGoodbyeMessage();
335+
$message->send();
348336
}
349-
} catch (Throwable) {
350-
// ignore, but could log
337+
338+
unset($this->boltProtocol);
351339
}
352340
}
353341

@@ -468,8 +456,6 @@ public function discardUnconsumedResults(): void
468456
$state = $this->getServerState();
469457
$this->logger?->log(LogLevel::DEBUG, "Server state before discard: {$state}");
470458

471-
// Skip discard if this connection was never used
472-
// Skip discard if this connection was never used
473459
if (!($this->connectionUsed['reader'] ?? false) && !($this->connectionUsed['writer'] ?? false)) {
474460
$this->logger?->log(LogLevel::DEBUG, 'Skipping discard - connection never used');
475461
$this->subscribedResults = [];

src/Bolt/BoltDriver.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
use Laudis\Neo4j\Contracts\DriverInterface;
2626
use Laudis\Neo4j\Contracts\SessionInterface;
2727
use Laudis\Neo4j\Databags\DriverConfiguration;
28+
use Laudis\Neo4j\Databags\ServerInfo;
2829
use Laudis\Neo4j\Databags\SessionConfiguration;
30+
use Laudis\Neo4j\Enum\AccessMode;
2931
use Laudis\Neo4j\Formatter\SummarizedResultFormatter;
3032
use Psr\Http\Message\UriInterface;
3133
use Psr\Log\LogLevel;
@@ -97,6 +99,37 @@ public function verifyConnectivity(?SessionConfiguration $config = null): bool
9799
return true;
98100
}
99101

102+
/**
103+
* Gets server information without running a query.
104+
*
105+
* Acquires a connection from the pool and extracts server metadata.
106+
* The pool handles all connection management, routing, and retries.
107+
*
108+
* @throws Exception if unable to acquire a connection
109+
*/
110+
public function getServerInfo(?SessionConfiguration $config = null): ServerInfo
111+
{
112+
$config ??= SessionConfiguration::default()->withAccessMode(AccessMode::READ());
113+
114+
$connectionGenerator = $this->pool->acquire($config);
115+
/**
116+
* @var BoltConnection $connection
117+
*
118+
* @psalm-suppress UnnecessaryVarAnnotation
119+
*/
120+
$connection = GeneratorHelper::getReturnFromGenerator($connectionGenerator);
121+
122+
try {
123+
return new ServerInfo(
124+
$connection->getServerAddress(),
125+
$connection->getProtocol(),
126+
$connection->getServerAgent()
127+
);
128+
} finally {
129+
$this->pool->release($connection);
130+
}
131+
}
132+
100133
public function closeConnections(): void
101134
{
102135
$this->pool->close();

src/Bolt/Session.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,6 @@ private function acquireConnection(TransactionConfiguration $config, SessionConf
172172
*/
173173
$connection = GeneratorHelper::getReturnFromGenerator($connectionGenerator);
174174

175-
// We try and let the server do the timeout management.
176-
// Since the client should not run indefinitely, we just add the client side by two, just in case
177175
$timeout = $config->getTimeout();
178176
if ($timeout !== null) {
179177
$timeout = ($timeout < 30) ? 30 : $timeout;

src/Contracts/DriverInterface.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace Laudis\Neo4j\Contracts;
1515

16+
use Laudis\Neo4j\Databags\ServerInfo;
1617
use Laudis\Neo4j\Databags\SessionConfiguration;
1718
use Laudis\Neo4j\Types\CypherList;
1819
use Laudis\Neo4j\Types\CypherMap;
@@ -35,6 +36,14 @@ public function createSession(?SessionConfiguration $config = null): SessionInte
3536
*/
3637
public function verifyConnectivity(?SessionConfiguration $config = null): bool;
3738

39+
/**
40+
* Gets server information without running a query.
41+
*
42+
* Acquires a connection from the pool and extracts server metadata.
43+
* The pool handles all connection management, routing, and retries.
44+
*/
45+
public function getServerInfo(?SessionConfiguration $config = null): ServerInfo;
46+
3847
/**
3948
* Closes all connections in the pool.
4049
*/

src/Databags/SessionConfiguration.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ public function __construct(
4141
private readonly ?AccessMode $accessMode = null,
4242
private readonly ?array $bookmarks = null,
4343
private readonly ?Neo4jLogger $logger = null,
44-
private readonly ?string $impersonatedUser = null
44+
private readonly ?string $impersonatedUser = null,
4545
) {
4646
}
47+
4748
public function withImpersonatedUser(?string $user): self
4849
{
4950
return new self(

src/Neo4j/Neo4jConnectionPool.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ private function getNextServer(RoutingTable $table, AccessMode $mode): Uri
193193
$servers = $table->getWithRole(RoutingRoles::FOLLOWER());
194194
}
195195

196+
if (empty($servers)) {
197+
throw new RuntimeException('No available servers found for the requested access mode');
198+
}
199+
196200
return Uri::create($servers[random_int(0, count($servers) - 1)]);
197201
}
198202

@@ -259,6 +263,16 @@ public function close(): void
259263
$this->cache->clear();
260264
}
261265

266+
/**
267+
* Forces a routing table refresh for the given configuration.
268+
* This will cause the next acquire() call to fetch a fresh routing table.
269+
*/
270+
public function refreshRoutingTable(SessionConfiguration $config): void
271+
{
272+
$key = $this->createKey($this->data, $config);
273+
$this->cache->delete($key);
274+
}
275+
262276
/**
263277
* @return Generator<string>
264278
*/

src/Neo4j/Neo4jDriver.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use function is_string;
2020

2121
use Laudis\Neo4j\Authentication\Authenticate;
22+
use Laudis\Neo4j\Bolt\BoltConnection;
2223
use Laudis\Neo4j\Bolt\Session;
2324
use Laudis\Neo4j\Common\DNSAddressResolver;
2425
use Laudis\Neo4j\Common\GeneratorHelper;
@@ -28,7 +29,9 @@
2829
use Laudis\Neo4j\Contracts\DriverInterface;
2930
use Laudis\Neo4j\Contracts\SessionInterface;
3031
use Laudis\Neo4j\Databags\DriverConfiguration;
32+
use Laudis\Neo4j\Databags\ServerInfo;
3133
use Laudis\Neo4j\Databags\SessionConfiguration;
34+
use Laudis\Neo4j\Enum\AccessMode;
3235
use Laudis\Neo4j\Formatter\SummarizedResultFormatter;
3336
use Psr\Http\Message\UriInterface;
3437
use Psr\Log\LogLevel;
@@ -75,6 +78,8 @@ public static function create(string|UriInterface $uri, ?DriverConfiguration $co
7578
/**
7679
* @psalm-mutation-free
7780
*
81+
* @psalm-suppress UnnecessaryVarAnnotation
82+
*
7883
* @throws Exception
7984
*/
8085
public function createSession(?SessionConfiguration $config = null): SessionInterface
@@ -99,6 +104,39 @@ public function verifyConnectivity(?SessionConfiguration $config = null): bool
99104
return true;
100105
}
101106

107+
/**
108+
* Gets server information without running a query.
109+
*
110+
* Acquires a connection from the pool and extracts server metadata.
111+
* The pool handles all connection management, routing, and retries.
112+
*
113+
* @throws Exception if unable to acquire a connection
114+
*/
115+
public function getServerInfo(?SessionConfiguration $config = null): ServerInfo
116+
{
117+
$config ??= SessionConfiguration::default()->withAccessMode(AccessMode::READ());
118+
119+
$this->pool->refreshRoutingTable($config);
120+
121+
$connectionGenerator = $this->pool->acquire($config);
122+
/**
123+
* @var BoltConnection $connection
124+
*
125+
* @psalm-suppress UnnecessaryVarAnnotation
126+
*/
127+
$connection = GeneratorHelper::getReturnFromGenerator($connectionGenerator);
128+
129+
try {
130+
return new ServerInfo(
131+
$connection->getServerAddress(),
132+
$connection->getProtocol(),
133+
$connection->getServerAgent()
134+
);
135+
} finally {
136+
$this->pool->release($connection);
137+
}
138+
}
139+
102140
public function closeConnections(): void
103141
{
104142
$this->pool->close();

testkit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit cbc816c9dbfe039c74e5f7a8104e3323c4d7dbf1

testkit-backend/src/Handlers/DriverClose.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public function __construct(MainRepository $repository)
3535
*/
3636
public function handle($request): DriverResponse
3737
{
38+
$driver = $this->repository->getDriver($request->getDriverId());
39+
$driver->closeConnections();
3840
$this->repository->removeDriver($request->getDriverId());
3941

4042
return new DriverResponse($request->getDriverId());

0 commit comments

Comments
 (0)