Skip to content

Commit afde1dd

Browse files
committed
winp
1 parent 4693ef9 commit afde1dd

14 files changed

+202
-166
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Query API
2+
3+
Usage example:
4+
5+
```php
6+
use Neo4j\QueryAPI\Neo4jQueryAPI;
7+
use Neo4j\QueryAPI\Objects\Authentication;
8+
9+
$client = Neo4jQueryAPI::login('https://myaddress.com', Authentication::bearer('mytokken'))
10+
```

src/AuthenticateInterface.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Neo4j\QueryAPI;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
7+
interface AuthenticateInterface
8+
{
9+
/**
10+
* Authenticates the request by returning a new instance of the request with the authentication information attached.
11+
*/
12+
public function authenticate(RequestInterface $request): RequestInterface;
13+
}

src/BasicAuthentication.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Neo4j\QueryAPI;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
use Psr\Http\Message\ResponseInterface;
7+
8+
class BasicAuthentication implements AuthenticateInterface
9+
{
10+
public function __construct(private string $username, private string $password)
11+
{}
12+
13+
public function authenticate(RequestInterface $request): RequestInterface
14+
{
15+
$authHeader = 'Basic ' . base64_encode($this->username . ':' . $this->password);
16+
return $request->withHeader('Authorization', $authHeader);
17+
}
18+
}

src/BearerAuthentication.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Neo4j\QueryAPI;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
7+
class BearerAuthentication implements AuthenticateInterface
8+
{
9+
public function __construct(private string $token)
10+
{}
11+
12+
public function authenticate(RequestInterface $request): RequestInterface
13+
{
14+
$authHeader = 'Bearer ' . $this->token;
15+
return $request->withHeader('Authorization', $authHeader);
16+
}
17+
}

src/Neo4jQueryAPI.php

Lines changed: 98 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,54 @@
22

33
namespace Neo4j\QueryAPI;
44

5-
use Exception;
65
use GuzzleHttp\Client;
7-
use Neo4j\QueryAPI\Objects\Auth;
6+
use GuzzleHttp\Psr7\Request;
7+
use GuzzleHttp\Psr7\Utils;
8+
use Neo4j\QueryAPI\Exception\Neo4jException;
89
use Neo4j\QueryAPI\Objects\Authentication;
10+
use Neo4j\QueryAPI\Objects\Bookmarks;
11+
use Neo4j\QueryAPI\Objects\ProfiledQueryPlan;
912
use Neo4j\QueryAPI\Objects\ProfiledQueryPlanArguments;
1013
use Neo4j\QueryAPI\Objects\ResultCounters;
11-
use Neo4j\QueryAPI\Objects\ProfiledQueryPlan;
14+
use Neo4j\QueryAPI\Objects\ResultSet;
1215
use Neo4j\QueryAPI\Results\ResultRow;
13-
use Neo4j\QueryAPI\Results\ResultSet;
14-
use Neo4j\QueryAPI\Exception\Neo4jException;
16+
use Psr\Http\Client\ClientInterface;
1517
use Psr\Http\Client\RequestExceptionInterface;
18+
use Psr\Http\Message\RequestInterface;
1619
use RuntimeException;
1720
use stdClass;
18-
use Neo4j\QueryAPI\Objects\Bookmarks;
1921

2022
class Neo4jQueryAPI
2123
{
22-
private Client $client;
24+
private ClientInterface $client;
25+
private AuthenticateInterface $auth;
2326

24-
public function __construct(Client $client)
27+
public function __construct(ClientInterface $client, AuthenticateInterface $auth)
2528
{
2629
$this->client = $client;
30+
$this->auth = $auth;
2731
}
2832

29-
public static function login(string $address, Authentication $auth): self
33+
/**
34+
* @throws \Exception
35+
*/
36+
public static function login(string $address, AuthenticateInterface $auth = null): self
3037
{
3138
$client = new Client([
3239
'base_uri' => rtrim($address, '/'),
3340
'timeout' => 10.0,
3441
'headers' => [
35-
'Authorization' => $auth->getHeader(),
3642
'Content-Type' => 'application/vnd.neo4j.query',
3743
'Accept' => 'application/vnd.neo4j.query',
3844
],
3945
]);
4046

41-
return new self($client);
47+
return new self($client, $auth ?? Authentication::fromEnvironment());
4248
}
4349

44-
4550
/**
51+
* Executes a Cypher query on the Neo4j database.
52+
*
4653
* @throws Neo4jException
4754
* @throws RequestExceptionInterface
4855
*/
@@ -59,68 +66,88 @@ public function run(string $cypher, array $parameters = [], string $database = '
5966
$payload['bookmarks'] = $bookmark->getBookmarks();
6067
}
6168

62-
$response = $this->client->request('POST', '/db/' . $database . '/query/v2', [
63-
'json' => $payload,
64-
]);
69+
70+
$request = new Request('POST', '/db/' . $database . '/query/v2');
71+
72+
$request = $this->auth->authenticate($request);
73+
74+
$request = $request->withHeader('Content-Type', 'application/json');
75+
76+
$request = $request->withBody(Utils::streamFor(json_encode($payload)));
77+
78+
$response = $this->client->sendRequest($request);
79+
6580

6681
$contents = $response->getBody()->getContents();
6782
$data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR);
68-
$ogm = new OGM();
6983

70-
$keys = $data['data']['fields'] ?? [];
71-
$values = $data['data']['values'] ?? []; // Ensure $values is an array
84+
return $this->parseResultSet($data);
85+
} catch (RequestExceptionInterface $e) {
86+
$this->handleException($e);
87+
}
88+
}
89+
90+
private function parseResultSet(array $data): ResultSet
91+
{
92+
$ogm = new OGM();
7293

73-
if (!is_array($values)) {
74-
throw new RuntimeException('Unexpected response format: values is not an array.');
75-
}
94+
$keys = $data['data']['fields'] ?? [];
95+
$values = $data['data']['values'] ?? [];
7696

77-
$rows = array_map(function ($resultRow) use ($ogm, $keys) {
78-
$data = [];
79-
foreach ($keys as $index => $key) {
80-
$fieldData = $resultRow[$index] ?? null;
81-
$data[$key] = $ogm->map($fieldData);
82-
}
83-
return new ResultRow($data);
84-
}, $values);
85-
$profile = isset($data['profiledQueryPlan']) ? $this->createProfileData($data['profiledQueryPlan']) : null;
86-
87-
$resultCounters = new ResultCounters(
88-
containsUpdates: $data['counters']['containsUpdates'] ?? false,
89-
nodesCreated: $data['counters']['nodesCreated'] ?? 0,
90-
nodesDeleted: $data['counters']['nodesDeleted'] ?? 0,
91-
propertiesSet: $data['counters']['propertiesSet'] ?? 0,
92-
relationshipsCreated: $data['counters']['relationshipsCreated'] ?? 0,
93-
relationshipsDeleted: $data['counters']['relationshipsDeleted'] ?? 0,
94-
labelsAdded: $data['counters']['labelsAdded'] ?? 0,
95-
labelsRemoved: $data['counters']['labelsRemoved'] ?? 0,
96-
indexesAdded: $data['counters']['indexesAdded'] ?? 0,
97-
indexesRemoved: $data['counters']['indexesRemoved'] ?? 0,
98-
constraintsAdded: $data['counters']['constraintsAdded'] ?? 0,
99-
constraintsRemoved: $data['counters']['constraintsRemoved'] ?? 0,
100-
containsSystemUpdates: $data['counters']['containsSystemUpdates'] ?? false,
101-
systemUpdates: $data['counters']['systemUpdates'] ?? 0
102-
);
103-
104-
return new ResultSet(
105-
$rows,
106-
$resultCounters,
107-
new Bookmarks($data['bookmarks'] ?? []),
108-
$profile
109-
);
110-
} catch (RequestExceptionInterface $e) {
111-
$response = $e->getResponse();
112-
if ($response !== null) {
113-
$contents = $response->getBody()->getContents();
114-
$errorResponse = json_decode($contents, true);
115-
throw Neo4jException::fromNeo4jResponse($errorResponse, $e);
97+
if (!is_array($values)) {
98+
throw new RuntimeException('Unexpected response format: values is not an array.');
99+
}
100+
101+
$rows = array_map(function ($resultRow) use ($ogm, $keys) {
102+
$row = [];
103+
foreach ($keys as $index => $key) {
104+
$fieldData = $resultRow[$index] ?? null;
105+
$row[$key] = $ogm->map($fieldData);
116106
}
117-
throw $e;
107+
return new ResultRow($row);
108+
}, $values);
109+
110+
$resultCounters = new ResultCounters(
111+
containsUpdates: $data['counters']['containsUpdates'] ?? false,
112+
nodesCreated: $data['counters']['nodesCreated'] ?? 0,
113+
nodesDeleted: $data['counters']['nodesDeleted'] ?? 0,
114+
propertiesSet: $data['counters']['propertiesSet'] ?? 0,
115+
relationshipsCreated: $data['counters']['relationshipsCreated'] ?? 0,
116+
relationshipsDeleted: $data['counters']['relationshipsDeleted'] ?? 0,
117+
labelsAdded: $data['counters']['labelsAdded'] ?? 0,
118+
labelsRemoved: $data['counters']['labelsRemoved'] ?? 0,
119+
indexesAdded: $data['counters']['indexesAdded'] ?? 0,
120+
indexesRemoved: $data['counters']['indexesRemoved'] ?? 0,
121+
constraintsAdded: $data['counters']['constraintsAdded'] ?? 0,
122+
constraintsRemoved: $data['counters']['constraintsRemoved'] ?? 0,
123+
containsSystemUpdates: $data['counters']['containsSystemUpdates'] ?? false,
124+
systemUpdates: $data['counters']['systemUpdates'] ?? 0
125+
);
126+
127+
$profile = isset($data['profiledQueryPlan']) ? $this->createProfileData($data['profiledQueryPlan']) : null;
128+
129+
return new ResultSet(
130+
$rows,
131+
$resultCounters,
132+
new Bookmarks($data['bookmarks'] ?? []),
133+
$profile
134+
);
135+
}
136+
137+
private function handleException(RequestExceptionInterface $e): void
138+
{
139+
$response = $e->getResponse();
140+
if ($response !== null) {
141+
$contents = $response->getBody()->getContents();
142+
$errorResponse = json_decode($contents, true);
143+
throw Neo4jException::fromNeo4jResponse($errorResponse, $e);
118144
}
145+
throw $e;
119146
}
120147

121148
public function beginTransaction(string $database = 'neo4j'): Transaction
122149
{
123-
$response = $this->client->post("/db/neo4j/query/v2/tx");
150+
$response = $this->client->sendRequest(new Request('POST', '/db/neo4j/query/v2/tx'));
124151

125152
$clusterAffinity = $response->getHeaderLine('neo4j-cluster-affinity');
126153
$responseData = json_decode($response->getBody(), true);
@@ -133,16 +160,12 @@ private function createProfileData(array $data): ProfiledQueryPlan
133160
{
134161
$ogm = new OGM();
135162

136-
// Map arguments using OGM
137-
$arguments = $data['arguments'];
138-
$mappedArguments = [];
139-
foreach ($arguments as $key => $value) {
163+
$mappedArguments = array_map(function ($value) use ($ogm) {
140164
if (is_array($value) && array_key_exists('$type', $value) && array_key_exists('_value', $value)) {
141-
$mappedArguments[$key] = $ogm->map($value);
142-
} else {
143-
$mappedArguments[$key] = $value;
165+
return $ogm->map($value);
144166
}
145-
}
167+
return $value;
168+
}, $data['arguments'] ?? []);
146169

147170
$queryArguments = new ProfiledQueryPlanArguments(
148171
globalMemory: $mappedArguments['GlobalMemory'] ?? null,
@@ -164,10 +187,9 @@ private function createProfileData(array $data): ProfiledQueryPlan
164187
id: $mappedArguments['Id'] ?? null,
165188
estimatedRows: $mappedArguments['EstimatedRows'] ?? null,
166189
planner: $mappedArguments['planner'] ?? null,
167-
rows: $mappedArguments['Rows' ?? null]
190+
rows: $mappedArguments['Rows'] ?? null
168191
);
169192

170-
$identifiers = $data['identifiers'] ?? [];
171193
$profiledQueryPlan = new ProfiledQueryPlan(
172194
$data['dbHits'],
173195
$data['records'],
@@ -179,15 +201,13 @@ private function createProfileData(array $data): ProfiledQueryPlan
179201
$data['operatorType'],
180202
$queryArguments,
181203
children: [],
182-
identifiers: $identifiers
204+
identifiers: $data['identifiers'] ?? []
183205
);
184-
// Process children recursively
185-
foreach ($data['children'] as $child) {
186-
$childQueryPlan = $this->createProfileData($child);
187-
$profiledQueryPlan->addChild($childQueryPlan);
206+
207+
foreach ($data['children'] ?? [] as $child) {
208+
$profiledQueryPlan->addChild($this->createProfileData($child));
188209
}
189210

190211
return $profiledQueryPlan;
191212
}
192-
193213
}

src/NoAuth.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Neo4j\QueryAPI;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
7+
class NoAuth implements AuthenticateInterface
8+
{
9+
public function authenticate(RequestInterface $request): RequestInterface
10+
{
11+
return $request;
12+
}
13+
}

0 commit comments

Comments
 (0)