1
1
<?php
2
2
3
+ declare (strict_types=1 );
4
+
3
5
/*
4
6
* This file is part of the Laudis Neo4j package.
5
7
*
11
13
12
14
namespace Laudis \Neo4j \Formatter \Specialised ;
13
15
16
+ use Closure ;
14
17
use Laudis \Neo4j \Contracts \ConnectionInterface ;
15
18
use Laudis \Neo4j \Contracts \PointInterface ;
16
19
use Laudis \Neo4j \Formatter \OGMFormatter ;
17
20
use Laudis \Neo4j \Http \HttpHelper ;
18
- use Laudis \Neo4j \Types \AbstractPropertyObject ;
19
21
use Laudis \Neo4j \Types \Cartesian3DPoint ;
20
22
use Laudis \Neo4j \Types \CartesianPoint ;
21
23
use Laudis \Neo4j \Types \CypherList ;
22
24
use Laudis \Neo4j \Types \CypherMap ;
23
25
use Laudis \Neo4j \Types \Node ;
24
26
use Laudis \Neo4j \Types \Path ;
25
27
use Laudis \Neo4j \Types \Relationship ;
28
+ use Laudis \Neo4j \Types \UnboundRelationship ;
26
29
use Laudis \Neo4j \Types \WGS843DPoint ;
27
30
use Laudis \Neo4j \Types \WGS84Point ;
28
31
use Psr \Http \Message \RequestInterface ;
38
41
*/
39
42
final class JoltHttpOGMTranslator
40
43
{
44
+ /** @var array<string, pure-callable(mixed):OGMTypes> */
41
45
private array $ rawToTypes ;
42
46
43
47
public function __construct ()
44
48
{
49
+ /** @psalm-suppress InvalidPropertyAssignmentValue */
45
50
$ this ->rawToTypes = [
46
51
'? ' => static fn (string $ value ): bool => strtolower ($ value ) === 'true ' ,
47
52
'Z ' => static fn (string $ value ): int => (int ) $ value ,
48
53
'R ' => static fn (string $ value ): float => (float ) $ value ,
49
54
'U ' => static fn (string $ value ): string => $ value ,
50
- 'T ' => fn (string $ value ): AbstractPropertyObject => $ this ->translateDateTime ($ value ),
51
- '@ ' => fn (string $ value ): PointInterface => $ this ->translatePoint ($ value ),
52
- '# ' => static function (string $ value ) {
53
- // TODO
54
- throw new UnexpectedValueException ('Binary data has not been implemented ' );
55
- },
56
- '[] ' => fn (array $ value ): CypherList => $ this ->translateList ($ value ),
57
- '{} ' => fn (stdClass $ value ): CypherMap => $ this ->translateMap ($ value ),
58
- '() ' => fn (array $ value ): Node => new Node ($ value [0 ], new CypherList ($ value [1 ]), $ this ->translateMap ($ value [2 ])),
59
- '-> ' => fn (array $ value ): Relationship => new Relationship ($ value [0 ], $ value [1 ], $ value [3 ], $ value [2 ], $ this ->translateMap ($ value [4 ])),
60
- '<- ' => fn (array $ value ): Relationship => new Relationship ($ value [0 ], $ value [3 ], $ value [1 ], $ value [2 ], $ this ->translateMap ($ value [4 ])),
61
- '.. ' => fn (array $ value ): Path => $ this ->translatePath ($ value ),
55
+ 'T ' => Closure::fromCallable ([$ this , 'translateDateTime ' ]),
56
+ '@ ' => Closure::fromCallable ([$ this , 'translatePoint ' ]),
57
+ '# ' => Closure::fromCallable ([$ this , 'translateBinary ' ]),
58
+ '[] ' => Closure::fromCallable ([$ this , 'translateList ' ]),
59
+ '{} ' => Closure::fromCallable ([$ this , 'translateMap ' ]),
60
+ '() ' => Closure::fromCallable ([$ this , 'translateNode ' ]),
61
+ '-> ' => Closure::fromCallable ([$ this , 'translateRightRelationship ' ]),
62
+ '<- ' => Closure::fromCallable ([$ this , 'translateLeftRelationship ' ]),
63
+ '.. ' => Closure::fromCallable ([$ this , 'translatePath ' ]),
62
64
];
63
65
}
64
66
@@ -74,10 +76,15 @@ public function formatHttpResult(
74
76
iterable $ statements
75
77
): CypherList {
76
78
$ allResults = [];
77
- // TODO: Lazy evaluation.
79
+ /** @var stdClass $result */
78
80
foreach ($ body ->results as $ result ) {
79
- $ fields = $ result ->header ->fields ;
81
+ /** @var stdClass $header */
82
+ $ header = $ result ->header ;
83
+ /** @var list<string> $fields */
84
+ $ fields = $ header ->fields ;
80
85
$ rows = [];
86
+
87
+ /** @var list<stdClass> $data */
81
88
foreach ($ result ->data as $ data ) {
82
89
$ row = [];
83
90
foreach ($ data as $ key => $ value ) {
@@ -91,20 +98,27 @@ public function formatHttpResult(
91
98
return new CypherList ($ allResults );
92
99
}
93
100
101
+ /**
102
+ * @return OGMTypes
103
+ */
94
104
private function translateJoltType (?stdClass $ value )
95
105
{
96
106
if (is_null ($ value )) {
97
107
return null ;
98
108
}
99
109
100
- [$ key , $ value ] = HttpHelper::splitJoltSingleton ($ value );
110
+ /** @var mixed $input */
111
+ [$ key , $ input ] = HttpHelper::splitJoltSingleton ($ value );
101
112
if (!isset ($ this ->rawToTypes [$ key ])) {
102
113
throw new UnexpectedValueException ('Unexpected Jolt key: ' .$ key );
103
114
}
104
115
105
- return $ this ->rawToTypes [$ key ]($ value );
116
+ return $ this ->rawToTypes [$ key ]($ input );
106
117
}
107
118
119
+ /**
120
+ * @return OGMTypes
121
+ */
108
122
private function translateDateTime (string $ datetime )
109
123
{
110
124
// TODO; They're in ISO format so shouldn't be too hard
@@ -125,28 +139,28 @@ private function translatePoint(string $value): PointInterface
125
139
126
140
if ($ srid === CartesianPoint::SRID ) {
127
141
return new CartesianPoint (
128
- $ coordinates [0 ],
129
- $ coordinates [1 ],
142
+ ( float ) $ coordinates [0 ],
143
+ ( float ) $ coordinates [1 ],
130
144
);
131
145
}
132
146
if ($ srid === Cartesian3DPoint::SRID ) {
133
147
return new Cartesian3DPoint (
134
- $ coordinates [0 ],
135
- $ coordinates [1 ],
136
- $ coordinates [2 ],
148
+ ( float ) $ coordinates [0 ],
149
+ ( float ) $ coordinates [1 ],
150
+ ( float ) $ coordinates [2 ],
137
151
);
138
152
}
139
153
if ($ srid === WGS84Point::SRID ) {
140
154
return new WGS84Point (
141
- $ coordinates [0 ],
142
- $ coordinates [1 ],
155
+ ( float ) $ coordinates [0 ],
156
+ ( float ) $ coordinates [1 ],
143
157
);
144
158
}
145
159
if ($ srid === WGS843DPoint::SRID ) {
146
160
return new WGS843DPoint (
147
- $ coordinates [0 ],
148
- $ coordinates [1 ],
149
- $ coordinates [2 ],
161
+ ( float ) $ coordinates [0 ],
162
+ ( float ) $ coordinates [1 ],
163
+ ( float ) $ coordinates [2 ],
150
164
);
151
165
}
152
166
throw new UnexpectedValueException ('A point with srid ' .$ srid .' has been returned, which has not been implemented. ' );
@@ -159,33 +173,40 @@ private function getSRID(string $value): int
159
173
throw new UnexpectedValueException ('Unexpected SRID string: ' .$ value );
160
174
}
161
175
176
+ /** @var array{0: string, 1: string} $matches */
162
177
return (int ) $ matches [1 ];
163
178
}
164
179
180
+ /**
181
+ * @return array{0: string, 1: string, 2: string} $coordinates
182
+ */
165
183
private function getCoordinates (string $ value ): array
166
184
{
167
185
$ matches = [];
168
- if (!preg_match ('/^POINT ?(Z?) ?\(([0-9\ . ]+)\)$/ ' , $ value , $ matches )) {
186
+ if (!preg_match ('/^POINT ?(Z?) ?\(([0-9. ]+)\)$/ ' , $ value , $ matches )) {
169
187
throw new UnexpectedValueException ('Unexpected point coordinates string: ' .$ value );
170
188
}
189
+ /** @var array{0: string, 1: string, 2: string} $matches */
171
190
$ coordinates = explode (' ' , $ matches [2 ]);
172
- if ($ matches [1 ] === 'Z ' ) {
173
- if (count ($ coordinates ) !== 3 ) {
174
- throw new UnexpectedValueException ('Expected 3 coordinates in string: ' .$ value );
175
- }
176
- } else {
177
- if (count ($ coordinates ) !== 2 ) {
178
- throw new UnexpectedValueException ('Expected 2 coordinates in string: ' .$ value );
179
- }
191
+ if ($ matches [1 ] === 'Z ' && count ($ coordinates ) !== 3 ) {
192
+ throw new UnexpectedValueException ('Expected 3 coordinates in string: ' .$ value );
193
+ }
194
+
195
+ if (count ($ coordinates ) !== 2 ) {
196
+ throw new UnexpectedValueException ('Expected 2 coordinates in string: ' .$ value );
180
197
}
181
198
182
199
return $ coordinates ;
183
200
}
184
201
202
+ /**
203
+ * @return CypherMap<OGMTypes>
204
+ */
185
205
private function translateMap (stdClass $ value ): CypherMap
186
206
{
187
207
return new CypherMap (
188
208
function () use ($ value ) {
209
+ /** @var stdClass|null $element */
189
210
foreach ((array ) $ value as $ key => $ element ) {
190
211
yield $ key => $ this ->translateJoltType ($ element );
191
212
}
@@ -197,25 +218,33 @@ private function translateList(array $value): CypherList
197
218
{
198
219
return new CypherList (
199
220
function () use ($ value ) {
221
+ /** @var stdClass|null $element */
200
222
foreach ($ value as $ element ) {
201
223
yield $ this ->translateJoltType ($ element );
202
224
}
203
225
}
204
226
);
205
227
}
206
228
207
- private function translatePath (array $ value )
229
+ /**
230
+ * @param list<stdClass> $value
231
+ */
232
+ private function translatePath (array $ value ): Path
208
233
{
209
234
$ nodes = [];
235
+ /** @var list<UnboundRelationship> $relations */
210
236
$ relations = [];
211
237
$ ids = [];
212
- foreach ($ value as $ i => $ nodeOrRelation ) {
238
+ foreach ($ value as $ nodeOrRelation ) {
239
+ /** @var Node|Relationship $nodeOrRelation */
213
240
$ nodeOrRelation = $ this ->translateJoltType ($ nodeOrRelation );
214
- if ($ i % 2 ) {
241
+
242
+ if ($ nodeOrRelation instanceof Relationship) {
215
243
$ relations [] = $ nodeOrRelation ;
216
244
} else {
217
245
$ nodes [] = $ nodeOrRelation ;
218
246
}
247
+
219
248
$ ids [] = $ nodeOrRelation ->getId ();
220
249
}
221
250
@@ -238,4 +267,33 @@ public function statementConfigOverride(): array
238
267
{
239
268
return [];
240
269
}
270
+
271
+ /**
272
+ * @param array{0: int, 1: list<string>, 2: stdClass} $value
273
+ */
274
+ private function translateNode (array $ value ): Node
275
+ {
276
+ return new Node ($ value [0 ], new CypherList ($ value [1 ]), $ this ->translateMap ($ value [2 ]));
277
+ }
278
+
279
+ /**
280
+ * @param array{0:int, 1: int, 2: string, 3:int, 4: stdClass} $value
281
+ */
282
+ private function translateRightRelationship (array $ value ): Relationship
283
+ {
284
+ return new Relationship ($ value [0 ], $ value [1 ], $ value [3 ], $ value [2 ], $ this ->translateMap ($ value [4 ]));
285
+ }
286
+
287
+ /**
288
+ * @param array{0:int, 1: int, 2: string, 3:int, 4: stdClass} $value
289
+ */
290
+ private function translateLeftRelationship (array $ value ): Relationship
291
+ {
292
+ return new Relationship ($ value [0 ], $ value [3 ], $ value [1 ], $ value [2 ], $ this ->translateMap ($ value [4 ]));
293
+ }
294
+
295
+ private function translateBinary (): Closure
296
+ {
297
+ throw new UnexpectedValueException ('Binary data has not been implemented ' );
298
+ }
241
299
}
0 commit comments