22
33namespace Neo4j \QueryAPI ;
44
5- use Exception ;
65use GuzzleHttp \Client ;
7- use GuzzleHttp \Exception \GuzzleException ;
86use GuzzleHttp \Exception \RequestException ;
9- use InvalidArgumentException ;
10- use Neo4j \QueryAPI \Objects \ChildQueryPlan ;
11- use Neo4j \QueryAPI \Objects \QueryArguments ;
12- use Neo4j \QueryAPI \Objects \ResultCounters ;
13- use Neo4j \QueryAPI \Objects \ProfiledQueryPlan ;
14- use Neo4j \QueryAPI \Results \ResultRow ;
7+ use Neo4j \QueryAPI \Enums \AccessMode ;
8+ use Neo4j \QueryAPI \Objects \Bookmarks ;
159use Neo4j \QueryAPI \Results \ResultSet ;
1610use Neo4j \QueryAPI \Exception \Neo4jException ;
1711use Psr \Http \Client \RequestExceptionInterface ;
18- use RuntimeException ;
19- use stdClass ;
20- use Neo4j \QueryAPI \Objects \Bookmarks ;
21- use Neo4j \QueryAPI \Enums \AccessMode ;
22-
12+ use Psr \Http \Message \ResponseInterface ;
2313
2414class Neo4jQueryAPI
2515{
26-
2716 private Client $ client ;
17+ private Configuration $ config ;
18+ private ResponseParser $ responseParser ;
2819
29- public function __construct (Client $ client )
20+ public function __construct (Configuration $ config , ResponseParser $ responseParser )
3021 {
31- $ this ->client = $ client ;
22+ $ this ->config = $ config ;
23+ $ this ->responseParser = $ responseParser ;
24+
25+ $ this ->client = new Client ([
26+ 'base_uri ' => rtrim ($ this ->config ->getBaseUrl (), '/ ' ),
27+ 'timeout ' => 10.0 ,
28+ 'headers ' => $ this ->config ->getDefaultHeaders (),
29+ ]);
3230 }
31+
3332 /**
34- * @api
33+ * Static method to create an instance with login details.
3534 */
3635 public static function login (string $ address , string $ username , string $ password ): self
3736 {
37+ $ authToken = base64_encode ("$ username: $ password " );
38+ $ config = (new Configuration ())
39+ ->setBaseUrl ($ address )
40+ ->setAuthToken ($ authToken );
3841
39-
40- $ client = new Client ([
41- 'base_uri ' => rtrim ($ address , '/ ' ),
42- 'timeout ' => 10.0 ,
43- 'headers ' => [
44- 'Authorization ' => 'Basic ' . base64_encode ("$ username: $ password " ),
45- 'Content-Type ' => 'application/vnd.neo4j.query ' ,
46- 'Accept ' => 'application/vnd.neo4j.query ' ,
47- ],
48- ]);
49-
50- return new self ($ client );
42+ return new self ($ config , new ResponseParser (new OGM ()));
5143 }
5244
5345 /**
54- * @throws Neo4jException
55- * @throws RequestExceptionInterface
56- * @api
46+ * Executes a Cypher query.
47+ *
48+ * @throws Neo4jException|RequestExceptionInterface
5749 */
5850 public function run (string $ cypher , array $ parameters = [], string $ database = 'neo4j ' , Bookmarks $ bookmark = null , ?string $ impersonatedUser = null , AccessMode $ accessMode = AccessMode::WRITE ): ResultSet
5951 {
6052 try {
6153 $ payload = [
6254 'statement ' => $ cypher ,
63- 'parameters ' => empty ($ parameters ) ? new stdClass () : $ parameters ,
55+ 'parameters ' => empty ($ parameters ) ? new \ stdClass () : $ parameters ,
6456 'includeCounters ' => true ,
6557 'accessMode ' => $ accessMode ->value ,
6658 ];
6759
68-
6960 if ($ bookmark !== null ) {
7061 $ payload ['bookmarks ' ] = $ bookmark ->getBookmarks ();
7162 }
63+
7264 if ($ impersonatedUser !== null ) {
7365 $ payload ['impersonatedUser ' ] = $ impersonatedUser ;
7466 }
7567
76- $ response = $ this ->client ->post ('/db/ ' . $ database . '/query/v2 ' , [
77- 'json ' => $ payload ,
78- ]);
68+ $ response = $ this ->client ->post ("/db/ {$ database }/query/v2 " , ['json ' => $ payload ]);
7969
80- $ data = json_decode ($ response ->getBody ()->getContents (), true );
81-
82- $ ogm = new OGM ();
83-
84- $ keys = $ data ['data ' ]['fields ' ];
85- $ values = $ data ['data ' ]['values ' ];
86-
87- $ rows = array_map (function ($ resultRow ) use ($ ogm , $ keys ) {
88- $ data = [];
89- foreach ($ keys as $ index => $ key ) {
90- $ fieldData = $ resultRow [$ index ] ?? null ;
91- $ data [$ key ] = $ ogm ->map ($ fieldData );
92- }
93- return new ResultRow ($ data );
94- }, $ values );
95-
96- $ profile = null ;
97- if (isset ($ data ['profiledQueryPlan ' ])) {
98- $ profile = $ this ->createProfileData ($ data ['profiledQueryPlan ' ]);
99- }
100-
101- $ resultCounters = new ResultCounters (
102- containsUpdates: $ data ['counters ' ]['containsUpdates ' ] ?? false ,
103- nodesCreated: $ data ['counters ' ]['nodesCreated ' ] ?? 0 ,
104- nodesDeleted: $ data ['counters ' ]['nodesDeleted ' ] ?? 0 ,
105- propertiesSet: $ data ['counters ' ]['propertiesSet ' ] ?? 0 ,
106- relationshipsCreated: $ data ['counters ' ]['relationshipsCreated ' ] ?? 0 ,
107- relationshipsDeleted: $ data ['counters ' ]['relationshipsDeleted ' ] ?? 0 ,
108- labelsAdded: $ data ['counters ' ]['labelsAdded ' ] ?? 0 ,
109- labelsRemoved: $ data ['counters ' ]['labelsRemoved ' ] ?? 0 ,
110- indexesAdded: $ data ['counters ' ]['indexesAdded ' ] ?? 0 ,
111- indexesRemoved: $ data ['counters ' ]['indexesRemoved ' ] ?? 0 ,
112- constraintsAdded: $ data ['counters ' ]['constraintsAdded ' ] ?? 0 ,
113- constraintsRemoved: $ data ['counters ' ]['constraintsRemoved ' ] ?? 0 ,
114- containsSystemUpdates: $ data ['counters ' ]['containsSystemUpdates ' ] ?? false ,
115- systemUpdates: $ data ['counters ' ]['systemUpdates ' ] ?? 0
116- );
117-
118- return new ResultSet (
119- $ rows ,
120- $ resultCounters ,
121- new Bookmarks ($ data ['bookmarks ' ] ?? []),
122- $ profile ,
123- $ accessMode
124- );
125- } catch (RequestExceptionInterface $ e ) {
126- error_log ("Request Exception: " . $ e ->getMessage ());
127-
128- $ response = $ e ->getResponse ();
129- if ($ response !== null ) {
130- $ contents = $ response ->getBody ()->getContents ();
131- $ errorResponse = json_decode ($ contents , true );
132- throw Neo4jException::fromNeo4jResponse ($ errorResponse , $ e );
133- }
134-
135-
136- throw new Neo4jException (['message ' => $ e ->getMessage ()], 500 , $ e );
70+ return $ this ->responseParser ->parseRunQueryResponse ($ response );
71+ } catch (RequestException $ e ) {
72+ $ this ->handleRequestException ($ e );
13773 }
13874 }
13975
140-
141-
14276 /**
143- * @api
77+ * Starts a transaction.
14478 */
14579 public function beginTransaction (string $ database = 'neo4j ' ): Transaction
14680 {
147- unset($ database );
148- $ response = $ this ->client ->post ("/db/neo4j/query/v2/tx " );
81+ $ response = $ this ->client ->post ("/db/ {$ database }/query/v2/tx " );
14982
15083 $ clusterAffinity = $ response ->getHeaderLine ('neo4j-cluster-affinity ' );
15184 $ responseData = json_decode ($ response ->getBody (), true );
@@ -154,49 +87,19 @@ public function beginTransaction(string $database = 'neo4j'): Transaction
15487 return new Transaction ($ this ->client , $ clusterAffinity , $ transactionId );
15588 }
15689
157- private function createProfileData (array $ data ): ProfiledQueryPlan
90+ /**
91+ * Handles request exceptions by parsing error details and throwing a Neo4jException.
92+ *
93+ * @throws Neo4jException
94+ */
95+ private function handleRequestException (RequestExceptionInterface $ e ): void
15896 {
159- $ arguments = $ data ['arguments ' ];
160-
161- $ queryArguments = new QueryArguments (
162- $ arguments ['globalMemory ' ] ?? 0 ,
163- $ arguments ['plannerImpl ' ] ?? '' ,
164- $ arguments ['memory ' ] ?? 0 ,
165- $ arguments ['stringRepresentation ' ] ?? '' ,
166- is_string ($ arguments ['runtime ' ] ?? '' ) ? $ arguments ['runtime ' ] : json_encode ($ arguments ['runtime ' ]),
167- $ arguments ['runtimeImpl ' ] ?? '' ,
168- $ arguments ['dbHits ' ] ?? 0 ,
169- $ arguments ['batchSize ' ] ?? 0 ,
170- $ arguments ['details ' ] ?? '' ,
171- $ arguments ['plannerVersion ' ] ?? '' ,
172- $ arguments ['pipelineInfo ' ] ?? '' ,
173- $ arguments ['runtimeVersion ' ] ?? '' ,
174- $ arguments ['id ' ] ?? 0 ,
175- $ arguments ['estimatedRows ' ] ?? 0.0 ,
176- is_string ($ arguments ['planner ' ] ?? '' ) ? $ arguments ['planner ' ] : json_encode ($ arguments ['planner ' ]),
177- $ arguments ['rows ' ] ?? 0
178- );
179-
180- $ profiledQueryPlan = new ProfiledQueryPlan (
181- $ data ['dbHits ' ],
182- $ data ['records ' ],
183- $ data ['hasPageCacheStats ' ],
184- $ data ['pageCacheHits ' ],
185- $ data ['pageCacheMisses ' ],
186- $ data ['pageCacheHitRatio ' ],
187- $ data ['time ' ],
188- $ data ['operatorType ' ],
189- $ queryArguments
190- );
191-
192- foreach ($ data ['children ' ] as $ child ) {
193- $ childQueryPlan = $ this ->createProfileData ($ child );
194-
195- $ profiledQueryPlan ->addChild ($ childQueryPlan );
97+ $ response = $ e ->getResponse ();
98+ if ($ response instanceof ResponseInterface) {
99+ $ errorResponse = json_decode ((string )$ response ->getBody (), true );
100+ throw Neo4jException::fromNeo4jResponse ($ errorResponse , $ e );
196101 }
197102
198- return $ profiledQueryPlan ;
103+ throw new Neo4jException ([ ' message ' => $ e -> getMessage ()], 500 , $ e ) ;
199104 }
200-
201-
202105}
0 commit comments