2
2
3
3
namespace Neo4j \QueryAPI ;
4
4
5
- use Exception ;
6
5
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 ;
8
9
use Neo4j \QueryAPI \Objects \Authentication ;
10
+ use Neo4j \QueryAPI \Objects \Bookmarks ;
11
+ use Neo4j \QueryAPI \Objects \ProfiledQueryPlan ;
9
12
use Neo4j \QueryAPI \Objects \ProfiledQueryPlanArguments ;
10
13
use Neo4j \QueryAPI \Objects \ResultCounters ;
11
- use Neo4j \QueryAPI \Objects \ProfiledQueryPlan ;
14
+ use Neo4j \QueryAPI \Objects \ResultSet ;
12
15
use Neo4j \QueryAPI \Results \ResultRow ;
13
- use Neo4j \QueryAPI \Results \ResultSet ;
14
- use Neo4j \QueryAPI \Exception \Neo4jException ;
16
+ use Psr \Http \Client \ClientInterface ;
15
17
use Psr \Http \Client \RequestExceptionInterface ;
18
+ use Psr \Http \Message \RequestInterface ;
16
19
use RuntimeException ;
17
20
use stdClass ;
18
- use Neo4j \QueryAPI \Objects \Bookmarks ;
19
21
20
22
class Neo4jQueryAPI
21
23
{
22
- private Client $ client ;
24
+ private ClientInterface $ client ;
25
+ private AuthenticateInterface $ auth ;
23
26
24
- public function __construct (Client $ client )
27
+ public function __construct (ClientInterface $ client, AuthenticateInterface $ auth )
25
28
{
26
29
$ this ->client = $ client ;
30
+ $ this ->auth = $ auth ;
27
31
}
28
32
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
30
37
{
31
38
$ client = new Client ([
32
39
'base_uri ' => rtrim ($ address , '/ ' ),
33
40
'timeout ' => 10.0 ,
34
41
'headers ' => [
35
- 'Authorization ' => $ auth ->getHeader (),
36
42
'Content-Type ' => 'application/vnd.neo4j.query ' ,
37
43
'Accept ' => 'application/vnd.neo4j.query ' ,
38
44
],
39
45
]);
40
46
41
- return new self ($ client );
47
+ return new self ($ client, $ auth ?? Authentication:: fromEnvironment () );
42
48
}
43
49
44
-
45
50
/**
51
+ * Executes a Cypher query on the Neo4j database.
52
+ *
46
53
* @throws Neo4jException
47
54
* @throws RequestExceptionInterface
48
55
*/
@@ -59,68 +66,88 @@ public function run(string $cypher, array $parameters = [], string $database = '
59
66
$ payload ['bookmarks ' ] = $ bookmark ->getBookmarks ();
60
67
}
61
68
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
+
65
80
66
81
$ contents = $ response ->getBody ()->getContents ();
67
82
$ data = json_decode ($ contents , true , flags: JSON_THROW_ON_ERROR );
68
- $ ogm = new OGM ();
69
83
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 ();
72
93
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 ' ] ?? [];
76
96
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 );
116
106
}
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 );
118
144
}
145
+ throw $ e ;
119
146
}
120
147
121
148
public function beginTransaction (string $ database = 'neo4j ' ): Transaction
122
149
{
123
- $ response = $ this ->client ->post ( " /db/neo4j/query/v2/tx " );
150
+ $ response = $ this ->client ->sendRequest ( new Request ( ' POST ' , ' /db/neo4j/query/v2/tx ' ) );
124
151
125
152
$ clusterAffinity = $ response ->getHeaderLine ('neo4j-cluster-affinity ' );
126
153
$ responseData = json_decode ($ response ->getBody (), true );
@@ -133,16 +160,12 @@ private function createProfileData(array $data): ProfiledQueryPlan
133
160
{
134
161
$ ogm = new OGM ();
135
162
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 ) {
140
164
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 );
144
166
}
145
- }
167
+ return $ value ;
168
+ }, $ data ['arguments ' ] ?? []);
146
169
147
170
$ queryArguments = new ProfiledQueryPlanArguments (
148
171
globalMemory: $ mappedArguments ['GlobalMemory ' ] ?? null ,
@@ -164,10 +187,9 @@ private function createProfileData(array $data): ProfiledQueryPlan
164
187
id: $ mappedArguments ['Id ' ] ?? null ,
165
188
estimatedRows: $ mappedArguments ['EstimatedRows ' ] ?? null ,
166
189
planner: $ mappedArguments ['planner ' ] ?? null ,
167
- rows: $ mappedArguments ['Rows ' ?? null ]
190
+ rows: $ mappedArguments ['Rows ' ] ?? null
168
191
);
169
192
170
- $ identifiers = $ data ['identifiers ' ] ?? [];
171
193
$ profiledQueryPlan = new ProfiledQueryPlan (
172
194
$ data ['dbHits ' ],
173
195
$ data ['records ' ],
@@ -179,15 +201,13 @@ private function createProfileData(array $data): ProfiledQueryPlan
179
201
$ data ['operatorType ' ],
180
202
$ queryArguments ,
181
203
children: [],
182
- identifiers: $ identifiers
204
+ identifiers: $ data [ ' identifiers ' ] ?? []
183
205
);
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 ));
188
209
}
189
210
190
211
return $ profiledQueryPlan ;
191
212
}
192
-
193
213
}
0 commit comments