-
-
Notifications
You must be signed in to change notification settings - Fork 562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nested async queries messes up the order of keys in arrays producing unwanted responses #80
Nested async queries messes up the order of keys in arrays producing unwanted responses #80
Conversation
Hi, can you tell me what promise adapter are you using? |
Hey, thanks for reporting. Looks like it's not the order that is wrong - it's just that keys are set as strings vs int. I don't think
As for adding event loop to tests - I wanted to leverage HHVM in Travis CI to test it, but couldn't find decent promise library for HHVM. Any hints how to integrate async stuff with Travis are welcome! |
I'm using ReactPromiseAdapter in a React event loop environment. The batch loader I'm using returns a promise which resolves into a value (the relation between entities in this case is one eventParticipant has one contactInfo and contactInfo has one companyInfo). I think the problem is the wrong order of index keys in array. For my examples I shortened the responses (the real one would contain 600 entities), Sorry for being unclear :). I tried var dumping the execution result before json_decoding and the keys are integers like they should. The print_r looks following where the un-ordering first occurs:
As for the unit testing I think we just need to new up the event loop and run that explicitly before asserting the test results. Please let me know if you need some more info! I can for example try to replicate this in a dummy repo with some mock data :). |
Sorry I am a bit slow today, so just to clarify... The question is who actually creates this array with gaps? Is it produced by your resolver/promise or produced by graphql? In the former case the result is expected. Here is a simple test which produces the same result as you describe: $a = [0 => 'a', 1 => 'b', 100 => 'z'];
echo json_encode($a); If your app returns array with such keys - it should be probably fixed in the user-land (but we can think about forcing arrays with valid keys). But if you return array with ordered integer keys without gaps and graphql re-orders them - this is a bug. So which is the case? |
Basically resolving promises async makes the array unordered. Here is a messy test to reproduce the problem. We are expecting an array with of public function testPromiseListsPreserveTheirArrayKeyOrder()
{
$loop = \React\EventLoop\Factory::create();
Executor::setPromiseAdapter(new ReactPromiseAdapter());
$testData = [
new \React\Promise\Promise(
function ($resolve) use ($loop) {
$loop->nextTick(
function () use ($resolve) {
$resolve(1);
}
);
}
),
new \React\Promise\Promise(
function ($resolve) use ($loop) {
$loop->nextTick(
function () use ($resolve, $loop) {
\React\Promise\resolve()->then( function() use ($resolve, $loop) {
$loop->nextTick(function () use ($resolve) {
$resolve(2);
});
});
}
);
}
),
new \React\Promise\Promise(
function ($resolve) use ($loop) {
$loop->nextTick(
function () use ($resolve) {
$resolve(3);
}
);
}
)
];
$expected = [ 'data' => [ 'nest' => [ 'test' => [ 1, 2, 3 ] ] ] ];
$expectedJson = json_encode($expected);
$data = ['test' => $testData];
$dataType = null;
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$dataType = new ObjectType(
[
'name' => 'DataType',
'fields' => function () use (&$testType, &$dataType, $data) {
return [
'test' => [
'type' => $testType
],
'nest' => [
'type' => $dataType,
'resolve' => function () use ($data) {
return $data;
}
]
];
}
]
);
$schema = new Schema(['query' => $dataType]);
$ast = Parser::parse('{ nest { test } }');
$result = Executor::execute($schema, $ast, $data);
$response = null;
$loop->run();
$result->then(function($value) use (&$response) {
$response = $value;
});
$this->assertEquals($expectedJson, json_encode($response->toArray()));
} |
Thanks a lot for the test case! Will check it as soon as I have a chance. |
I investigated bit further and this could be a problem (or indented behavior) in ReactPHP itself. I opened a issue there : reactphp/promise#74 . If it's indented the question is should we ever expect a unordered array as a list? As it should be a list by GraphQl spec. Javascript doesn't have this problem as arrays are always in order and arrays with gaps are converted to json arrays correctly. Thanks a thousand for help! :) |
I suspected that it could be caused by ReactPHP |
@lordthorzonus I think we should add |
@mcg-web You are right. function all(array $promisesOrValues)
{
$result = \React\Promise\all($promisesOrValues);
return $result->then(function($values) use ($promisesOrValues) {
$newResult = [];
foreach ($promisesOrValues as $key => $value) {
$newResult[$key] = $values[$key];
}
return $newResult;
});
} Is it what you mean? |
Yes @vladar, you got it 👍 |
@lordthorzonus I don't think we should add it as dev dependency. I don't want to create impression that ReactPHP is somwhow required for development when using this lib. It is 100% optional stuff and composer What we could do with reagrds to tests:
If you do not want to bother with this - just add tests for ReactPHP that you find appropriate - and exclude them in default P.S. Glad that you are using this async stuff! %) |
54ece56
to
e3a864f
Compare
b3cf2cd
to
8626e0b
Compare
@vladar PR updated. Added tests and fixed the
|
@lordthorzonus Great work. Thanks! |
When batch loading multiple nested relations in an async environment the values of the arrays aren't resolved in a sync order anymore which produces php arrays whose indexes aren't ordered properly. This causes json_encode to evaluate them as associative arrays and add the keys to the json response.
For example a query:
Where all the relations are one -> one and batch loaded it produces following json:
When the expected result would be:
I'm not sure if this is the right approach to solve the problem. But at least it removes it :). If we want to add tests we probably need to add the event loop as dev dependency?