This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * This file is a part of GraphQL project. |
||
4 | * |
||
5 | * @author Philipp Melab <[email protected]> |
||
6 | * created: 6/14/17 8:16 PM |
||
7 | */ |
||
8 | |||
9 | namespace Youshido\Tests\Schema; |
||
10 | |||
11 | use Youshido\GraphQL\Config\Schema\SchemaConfig; |
||
12 | use Youshido\GraphQL\Execution\DeferredResolver; |
||
13 | use Youshido\GraphQL\Execution\Processor; |
||
14 | use Youshido\GraphQL\Field\Field; |
||
15 | use Youshido\GraphQL\Schema\AbstractSchema; |
||
16 | use Youshido\GraphQL\Type\ListType\ListType; |
||
17 | use Youshido\GraphQL\Type\Object\AbstractObjectType; |
||
18 | use Youshido\GraphQL\Type\Object\ObjectType; |
||
19 | use Youshido\GraphQL\Type\Scalar\StringType; |
||
20 | |||
21 | /** |
||
22 | * Interface definition for a database service that will be mocked. |
||
23 | */ |
||
24 | interface DeferredDatabase |
||
25 | { |
||
26 | |||
27 | /** |
||
28 | * Retrieve a list of users by their id. |
||
29 | * |
||
30 | * @param string[] $ids |
||
31 | * The user ids. |
||
32 | * |
||
33 | * @return array |
||
34 | * A 2-dimensional array of user objects, keyed by their id. |
||
35 | */ |
||
36 | public function query(array $ids); |
||
37 | } |
||
38 | |||
39 | /** |
||
40 | * Buffers queries until they are actually required. |
||
41 | */ |
||
42 | class DeferredQueryBuffer |
||
0 ignored issues
–
show
|
|||
43 | { |
||
44 | |||
45 | protected $database; |
||
46 | |||
47 | protected $buffer = []; |
||
48 | |||
49 | protected $results = []; |
||
50 | |||
51 | public function __construct(DeferredDatabase $database) |
||
52 | { |
||
53 | $this->database = $database; |
||
54 | } |
||
55 | |||
56 | public function add(array $ids) |
||
57 | { |
||
58 | $key = md5(serialize($ids)); |
||
59 | $this->buffer[$key] = $ids; |
||
60 | |||
61 | return function () use ($key) { |
||
62 | return $this->fetch($key); |
||
63 | }; |
||
64 | } |
||
65 | |||
66 | protected function fetch($resultId) |
||
67 | { |
||
68 | if (!array_key_exists($resultId, $this->results)) { |
||
69 | $query = array_unique( |
||
70 | array_reduce($this->buffer, 'array_merge', []) |
||
71 | ); |
||
72 | sort($query); |
||
73 | $result = $this->database->query($query); |
||
74 | foreach ($this->buffer as $index => $query) { |
||
75 | foreach ($query as $id) { |
||
76 | $this->results[$index][$id] = $result[$id]; |
||
77 | } |
||
78 | unset($this->buffer[$index]); |
||
79 | } |
||
80 | } |
||
81 | |||
82 | return $this->results[$resultId]; |
||
83 | } |
||
84 | } |
||
85 | |||
86 | class DeferredUserType extends AbstractObjectType |
||
0 ignored issues
–
show
|
|||
87 | { |
||
88 | |||
89 | /** |
||
90 | * @var \Youshido\Tests\Schema\DeferredQueryBuffer |
||
91 | */ |
||
92 | protected $database; |
||
93 | |||
94 | public function __construct(DeferredQueryBuffer $database) |
||
95 | { |
||
96 | $this->database = $database; |
||
97 | parent::__construct(); |
||
98 | } |
||
99 | |||
100 | |||
101 | public function build($config) |
||
102 | { |
||
103 | $config->addField( |
||
104 | new Field( |
||
105 | [ |
||
106 | 'name' => 'name', |
||
107 | 'type' => new StringType(), |
||
108 | 'resolve' => function ($value) { |
||
109 | return $value['name']; |
||
110 | }, |
||
111 | ] |
||
112 | ) |
||
113 | ); |
||
114 | |||
115 | $config->addField( |
||
116 | new Field( |
||
117 | [ |
||
118 | 'name' => 'friends', |
||
119 | 'type' => new ListType(new DeferredUserType($this->database)), |
||
120 | 'resolve' => function ($value) { |
||
121 | return new DeferredResolver( |
||
122 | $this->database->add($value['friends']) |
||
123 | ); |
||
124 | }, |
||
125 | ] |
||
126 | ) |
||
127 | ); |
||
128 | |||
129 | $config->addField( |
||
130 | new Field( |
||
131 | [ |
||
132 | 'name' => 'foes', |
||
133 | 'type' => new ListType(new DeferredUserType($this->database)), |
||
134 | 'resolve' => function ($value) { |
||
135 | return new DeferredResolver( |
||
136 | $this->database->add($value['foes']) |
||
137 | ); |
||
138 | }, |
||
139 | ] |
||
140 | ) |
||
141 | ); |
||
142 | } |
||
143 | } |
||
144 | |||
145 | class DeferredSchema extends AbstractSchema |
||
0 ignored issues
–
show
|
|||
146 | { |
||
147 | |||
148 | public function __construct(DeferredQueryBuffer $buffer) |
||
149 | { |
||
150 | $usersField = new Field( |
||
151 | [ |
||
152 | 'name' => 'users', |
||
153 | 'type' => new ListType(new DeferredUserType($buffer)), |
||
154 | 'resolve' => function ($value, $args) use ($buffer) { |
||
155 | return new DeferredResolver($buffer->add($args['ids'])); |
||
156 | }, |
||
157 | ] |
||
158 | ); |
||
159 | |||
160 | $usersField->addArgument( |
||
161 | 'ids', |
||
162 | [ |
||
163 | 'type' => new ListType(new StringType()), |
||
164 | ] |
||
165 | ); |
||
166 | parent::__construct( |
||
167 | [ |
||
168 | 'query' => new ObjectType( |
||
169 | [ |
||
170 | 'name' => 'RootQuery', |
||
171 | 'fields' => [$usersField], |
||
172 | ] |
||
173 | ), |
||
174 | ] |
||
175 | ); |
||
176 | } |
||
177 | |||
178 | |||
179 | public function build(SchemaConfig $config) |
||
180 | { |
||
181 | } |
||
182 | |||
183 | } |
||
184 | |||
185 | |||
186 | /** |
||
187 | * Test the deferred resolving under different circumstances. |
||
188 | */ |
||
189 | class DeferredTest extends \PHPUnit_Framework_TestCase |
||
0 ignored issues
–
show
|
|||
190 | { |
||
191 | |||
192 | /** |
||
193 | * @var Processor |
||
194 | */ |
||
195 | protected $processor; |
||
196 | |||
197 | protected function query($query, DeferredQueryBuffer $buffer) |
||
198 | { |
||
199 | $processor = new Processor(new DeferredSchema($buffer)); |
||
200 | $processor->processPayload($query, []); |
||
201 | |||
202 | return $processor->getResponseData(); |
||
203 | } |
||
204 | |||
205 | |||
206 | /** |
||
207 | * Test a simple single deferred field. |
||
208 | */ |
||
209 | View Code Duplication | public function testSingleResolve() |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
210 | { |
||
211 | $query = 'query { |
||
212 | users(ids: ["a", "b"]) { |
||
213 | name |
||
214 | } |
||
215 | }'; |
||
216 | $database = $this->prophesize(DeferredDatabase::class); |
||
217 | |||
218 | $database->query(['a', 'b'])->willReturn( |
||
219 | [ |
||
220 | 'a' => ['id' => 'a', 'name' => 'User A'], |
||
221 | 'b' => ['id' => 'b', 'name' => 'User B'], |
||
222 | ] |
||
223 | )->shouldBeCalledTimes(1); |
||
224 | |||
225 | $result = $this->query( |
||
226 | $query, |
||
227 | new DeferredQueryBuffer($database->reveal()) |
||
228 | ); |
||
229 | |||
230 | $database->checkProphecyMethodsPredictions(); |
||
231 | |||
232 | $this->assertEquals( |
||
233 | [ |
||
234 | 'users' => [ |
||
235 | ['name' => 'User A'], |
||
236 | ['name' => 'User B'], |
||
237 | ], |
||
238 | ], |
||
239 | $result['data'], |
||
240 | 'Retrieved correct data.' |
||
241 | ); |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Test if multiple calls to the same field result in a single query. |
||
246 | */ |
||
247 | View Code Duplication | public function testMultiResolve() |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
248 | { |
||
249 | |||
250 | $query = 'query { |
||
251 | a:users(ids: ["a"]) { |
||
252 | name |
||
253 | } |
||
254 | b:users(ids: ["b"]) { |
||
255 | name |
||
256 | } |
||
257 | }'; |
||
258 | |||
259 | $database = $this->prophesize(DeferredDatabase::class); |
||
260 | |||
261 | $database->query(['a', 'b'])->willReturn( |
||
262 | [ |
||
263 | 'a' => ['id' => 'a', 'name' => 'User A'], |
||
264 | 'b' => ['id' => 'b', 'name' => 'User B'], |
||
265 | ] |
||
266 | )->shouldBeCalledTimes(1); |
||
267 | |||
268 | $result = $this->query( |
||
269 | $query, |
||
270 | new DeferredQueryBuffer($database->reveal()) |
||
271 | ); |
||
272 | |||
273 | $database->checkProphecyMethodsPredictions(); |
||
274 | |||
275 | $this->assertEquals( |
||
276 | [ |
||
277 | 'a' => [ |
||
278 | ['name' => 'User A'], |
||
279 | ], |
||
280 | 'b' => [ |
||
281 | ['name' => 'User B'], |
||
282 | ], |
||
283 | ], |
||
284 | $result['data'], |
||
285 | 'Retrieved correct data.' |
||
286 | ); |
||
287 | |||
288 | } |
||
289 | |||
290 | /** |
||
291 | * Test if recursive deferred resolvers work properly. |
||
292 | */ |
||
293 | public function testRecursiveResolve() |
||
294 | { |
||
295 | |||
296 | $query = 'query { |
||
297 | a:users(ids: ["a"]) { |
||
298 | name |
||
299 | friends { |
||
300 | name |
||
301 | } |
||
302 | } |
||
303 | }'; |
||
304 | |||
305 | $database = $this->prophesize(DeferredDatabase::class); |
||
306 | |||
307 | $database->query(['a'])->willReturn( |
||
308 | [ |
||
309 | 'a' => ['id' => 'a', 'name' => 'User A', 'friends' => ['b', 'c']], |
||
310 | ] |
||
311 | )->shouldBeCalledTimes(1); |
||
312 | |||
313 | $database->query(['b', 'c'])->willReturn( |
||
314 | [ |
||
315 | 'b' => ['id' => 'b', 'name' => 'User B'], |
||
316 | 'c' => ['id' => 'c', 'name' => 'User C'], |
||
317 | ] |
||
318 | ); |
||
319 | |||
320 | $result = $this->query( |
||
321 | $query, |
||
322 | new DeferredQueryBuffer($database->reveal()) |
||
323 | ); |
||
324 | |||
325 | $database->checkProphecyMethodsPredictions(); |
||
326 | |||
327 | $this->assertEquals( |
||
328 | [ |
||
329 | 'a' => [ |
||
330 | [ |
||
331 | 'name' => 'User A', |
||
332 | 'friends' => [ |
||
333 | ['name' => 'User B'], |
||
334 | ['name' => 'User C'], |
||
335 | ], |
||
336 | ], |
||
337 | ], |
||
338 | ], |
||
339 | $result['data'], |
||
340 | 'Retrieved correct data.' |
||
341 | ); |
||
342 | } |
||
343 | |||
344 | /** |
||
345 | * Test if multiple deferred resolvers are optimized into two queries. |
||
346 | */ |
||
347 | public function testMultiRecursiveResolve() |
||
348 | { |
||
349 | |||
350 | $query = 'query { |
||
351 | a:users(ids: ["a"]) { |
||
352 | name |
||
353 | friends { |
||
354 | name |
||
355 | } |
||
356 | foes { |
||
357 | name |
||
358 | } |
||
359 | } |
||
360 | b:users(ids: ["b"]) { |
||
361 | name |
||
362 | friends { |
||
363 | name |
||
364 | } |
||
365 | foes { |
||
366 | name |
||
367 | } |
||
368 | } |
||
369 | }'; |
||
370 | |||
371 | $database = $this->prophesize(DeferredDatabase::class); |
||
372 | |||
373 | $database->query(['a', 'b'])->willReturn( |
||
374 | [ |
||
375 | 'a' => [ |
||
376 | 'id' => 'a', |
||
377 | 'name' => 'User A', |
||
378 | 'friends' => ['b', 'c'], |
||
379 | 'foes' => ['d', 'e'], |
||
380 | ], |
||
381 | 'b' => [ |
||
382 | 'id' => 'b', |
||
383 | 'name' => 'User B', |
||
384 | 'friends' => ['a'], |
||
385 | 'foes' => ['c'], |
||
386 | ], |
||
387 | ] |
||
388 | )->shouldBeCalledTimes(1); |
||
389 | |||
390 | $database->query(['a', 'b', 'c', 'd', 'e'])->willReturn( |
||
391 | [ |
||
392 | 'a' => ['id' => 'a', 'name' => 'User A'], |
||
393 | 'b' => ['id' => 'b', 'name' => 'User B'], |
||
394 | 'c' => ['id' => 'c', 'name' => 'User C'], |
||
395 | 'd' => ['id' => 'd', 'name' => 'User D'], |
||
396 | 'e' => ['id' => 'e', 'name' => 'User E'], |
||
397 | ] |
||
398 | )->shouldBeCalledTimes(1); |
||
399 | |||
400 | $result = $this->query( |
||
401 | $query, |
||
402 | new DeferredQueryBuffer($database->reveal()) |
||
403 | ); |
||
404 | |||
405 | $database->checkProphecyMethodsPredictions(); |
||
406 | |||
407 | $this->assertEquals( |
||
408 | [ |
||
409 | 'a' => [ |
||
410 | [ |
||
411 | 'name' => 'User A', |
||
412 | 'friends' => [ |
||
413 | ['name' => 'User B'], |
||
414 | ['name' => 'User C'], |
||
415 | ], |
||
416 | 'foes' => [ |
||
417 | ['name' => 'User D'], |
||
418 | ['name' => 'User E'], |
||
419 | ], |
||
420 | ], |
||
421 | ], |
||
422 | 'b' => [ |
||
423 | [ |
||
424 | 'name' => 'User B', |
||
425 | 'friends' => [ |
||
426 | ['name' => 'User A'], |
||
427 | ], |
||
428 | 'foes' => [ |
||
429 | ['name' => 'User C'], |
||
430 | ], |
||
431 | ], |
||
432 | ], |
||
433 | ], |
||
434 | $result['data'], |
||
435 | 'Retrieved data is correct.' |
||
436 | ); |
||
437 | } |
||
438 | } |
Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.