Skip to content

Instantly share code, notes, and snippets.

@adri
Created February 8, 2018 07:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adri/3aeba45966573f93c3ddd6c8a37a96fa to your computer and use it in GitHub Desktop.
Save adri/3aeba45966573f93c3ddd6c8a37a96fa to your computer and use it in GitHub Desktop.
Concurrent GraphQL resolvers in PHP POC
<?php
// Test this using following command
// php reactphp.php
// curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
// curl http://localhost:8080 -d '{"query": "mutation { sum(x: 2, y: 2) }" }'
require_once __DIR__ . '/../vendor/autoload.php';
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
use GraphQL\GraphQL;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use leinonen\DataLoader\CacheMap;
use leinonen\DataLoader\Dataloader;
use leinonen\DataLoader\DataLoaderOptions;
use OpenCensus\Trace\Exporter\StackdriverExporter;
use OpenCensus\Trace\Tracer;
use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\Factory;
use React\Http\Response;
$eventLoop = Factory::create();
$cacheMap = new CacheMap();
$customers = [
'customer_1' => ['name' => 'Customer 1', 'payouts' => ['payout_1', 'payout_2']],
'customer_2' => ['name' => 'Customer 2', 'payouts' => ['payout_3', 'payout_4']],
];
$payouts = [
'payout_1' => ['name' => 'Payout 1', 'payment' => ['id' => 'payment_123']],
'payout_2' => ['name' => 'Payout 2', 'payment' => ['id' => 'payment_234']],
'payout_3' => ['name' => 'Payout 3', 'payment' => ['id' => 'payment_345']],
'payout_4' => ['name' => 'Payout 4', 'payment' => ['id' => 'payment_456']],
];
$payoutQueries = [
'available' => ['payout_1', 'payout_3'],
'paid' => ['payout_2', 'payout_4'],
];
$payments = [
'payment_123' => ['name' => 'Payment 123'],
'payment_234' => ['name' => 'Payment 234'],
'payment_345' => ['name' => 'Payment 345'],
'payment_456' => ['name' => 'Payment 456'],
];
function makeLoader($eventLoop, $cacheMap, $data, $name) {
return new DataLoader(
function ($keys) use ($eventLoop, $data, $name) {
opencensus_trace_begin('load_'.$name.'_'.json_encode($keys));
$ordered = array_map(function ($key) use($data) { return $data[$key]; }, $keys);
return \React\Promise\Timer\resolve(1.0, $eventLoop)->then(function () use ($name, $ordered) {
opencensus_trace_finish();
return $ordered;
});
},
$eventLoop,
$cacheMap,
new DataLoaderOptions(null, true, true)
);
}
$customerLoader = makeLoader($eventLoop, $cacheMap, $customers, 'customer');
$allPayoutsLoader = makeLoader($eventLoop, $cacheMap, $payoutQueries, 'allPayouts');
$payoutLoader = makeLoader($eventLoop, $cacheMap, $payouts, 'payout');
$paymentLoader = makeLoader($eventLoop, $cacheMap, $payments, 'payment');
$paymentType = new ObjectType([
'name' => 'Payment',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function ($payment, $args) {
return $payment['name'];
}
],
],
]);
$payoutType = new ObjectType([
'name' => 'Payout',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function ($payout, $args) {
return $payout['name'];
}
],
'payment' => [
'type' => $paymentType,
'resolve' => function ($payout) use($paymentLoader) {
return $paymentLoader->load($payout['payment']['id']);
}
],
],
]);
$customerType = new ObjectType([
'name' => 'Customer',
'fields' => [
'name' => [
'type' => Type::string(),
'resolve' => function ($customer, $args) {
return $customer['name'];
}
],
'payouts' => [
'type' => Type::listOf($payoutType),
'resolve' => function ($customer) use ($payoutLoader) {
return $payoutLoader->loadMany($customer['payouts']);
}
],
],
]);
$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'customer' => [
'type' => $customerType,
'args' => [
'id' => ['type' => Type::nonNull(Type::string())],
],
'resolve' => function ($root, $args, $info) use ($customerLoader) {
return $customerLoader->load($args['id']);
}
],
'allPayouts' => [
'type' => Type::listOf($payoutType),
'args' => [
'filter' => ['type' => Type::nonNull(Type::string())],
],
'resolve' => function ($root, $args) use ($allPayoutsLoader, $payoutLoader) {
return $allPayoutsLoader
->load($args['filter'])
->then(function($payoutIds) use($payoutLoader) {
return $payoutLoader->loadMany($payoutIds);
});
}
],
],
]);
$schema = new Schema([
'query' => $queryType,
]);
$server = new \React\Http\Server(function (ServerRequestInterface $request) use ($schema) {
// Start the request tracing for this request
$exporter = new StackdriverExporter([
'async' => true,
'clientConfig' => [
'projectId' => 'eighth-orbit-193111',
'keyFilePath' => __DIR__ . '/../trace-test.json',
]
]);
Tracer::start($exporter);
$input = json_decode((string) $request->getBody(), true);
$result = GraphQL::promiseToExecute(
new ReactPromiseAdapter(),
$schema,
$input['query'],
[],
null,
$input['variables'] ?? []
);
return $result
->then(function($result) {
return new Response(
200,
[ 'Content-Type' => 'application/json' ],
json_encode($result)
);
}, function($error) {
return new Response(
500,
[ 'Content-Type' => 'application/json' ],
json_encode($error)
);
});
});
$socket = new React\Socket\Server(8080, $eventLoop);
$server->listen($socket);
$eventLoop->run();
@adri
Copy link
Author

adri commented Feb 11, 2018

stackdriver

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment