Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions Controller/GraphController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,32 @@ class GraphController extends Controller
{
public function endpointAction(Request $request)
{
if ($request->query->has('batch')) {
$data = $this->processBatchQuery($request);
} else {
$data = $this->processNormalQuery($request);
}
$payload = $this->processNormalQuery($request);

return new JsonResponse($data, 200);
return new JsonResponse($payload, 200);
}

private function processBatchQuery(Request $request)
public function batchEndpointAction(Request $request)
{
$params = $this->get('overblog_graphql.request_batch_parser')->parse($request);
$data = [];
$payloads = $this->processBatchQuery($request);

return new JsonResponse($payloads, 200);
}

foreach ($params as $i => $entry) {
$data[$i] = $this->get('overblog_graphql.request_executor')->execute($entry)->toArray();
private function processBatchQuery(Request $request)
{
$queries = $this->get('overblog_graphql.request_batch_parser')->parse($request);
$payloads = [];

foreach ($queries as $query) {
$payloadResult = $this->get('overblog_graphql.request_executor')->execute([
'query' => $query['query'],
'variables' => $query['variables'],
]);
$payloads[] = ['id' => $query['id'], 'payload' => $payloadResult->toArray()];
}

return $data;
return $payloads;
}

private function processNormalQuery(Request $request)
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
OverblogGraphQLBundle
======================

This Bundle provide integration [GraphQL](https://facebook.github.io/graphql/) using [webonyx/graphql-php](https://github.com/webonyx/graphql-php)
This Symfony 2 / 3 bundle provide integration [GraphQL](https://facebook.github.io/graphql/) using [webonyx/graphql-php](https://github.com/webonyx/graphql-php)
and [GraphQL Relay](https://facebook.github.io/relay/docs/graphql-relay-specification.html).
It also supports batching using libs like [ReactRelayNetworkLayer](https://github.com/nodkz/react-relay-network-layer).

[![Build Status](https://travis-ci.org/overblog/GraphQLBundle.svg?branch=master)](https://travis-ci.org/overblog/GraphQLBundle)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/overblog/GraphQLBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/overblog/GraphQLBundle/?branch=master)
Expand Down Expand Up @@ -877,6 +878,12 @@ Expression | Description | Scope

**Tips**: the expression language service can be custom using bundle configuration.

Batching
---------

Batching can help decrease io between server and client.
The default route of batching is `/batch`.

Contribute
----------

Expand Down
29 changes: 17 additions & 12 deletions Request/BatchParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@

class BatchParser implements ParserInterface
{
const PARAM_ID = 'id';

private static $queriesDefaultValue = [
self::PARAM_ID => null,
self::PARAM_QUERY => null,
self::PARAM_VARIABLES => null,
];

/**
* @param Request $request
*
Expand All @@ -24,24 +32,21 @@ class BatchParser implements ParserInterface
public function parse(Request $request)
{
// Extracts the GraphQL request parameters
$data = $this->getParsedBody($request);
$queries = $this->getParsedBody($request);

if (empty($data)) {
if (empty($queries)) {
throw new BadRequestHttpException('Must provide at least one valid query.');
}

foreach ($data as $i => &$entry) {
if (empty($entry[static::PARAM_QUERY]) || !is_string($entry[static::PARAM_QUERY])) {
throw new BadRequestHttpException(sprintf('No valid query found in node "%s"', $i));
}
foreach ($queries as $i => &$query) {
$query = $query + self::$queriesDefaultValue;

$entry = $entry + [
static::PARAM_VARIABLES => null,
static::PARAM_OPERATION_NAME => null,
];
if (!is_string($query[static::PARAM_QUERY])) {
throw new BadRequestHttpException(sprintf('%s is not a valid query', json_encode($query[static::PARAM_QUERY])));
}
}

return $data;
return $queries;
}

/**
Expand All @@ -57,7 +62,7 @@ private function getParsedBody(Request $request)

// JSON object
if ($type !== static::CONTENT_TYPE_JSON) {
throw new BadRequestHttpException(sprintf('Only request with content type "%" is accepted.', static::CONTENT_TYPE_JSON));
throw new BadRequestHttpException(sprintf('Only request with content type "%s" is accepted.', static::CONTENT_TYPE_JSON));
}

$parsedBody = json_decode($request->getContent(), true);
Expand Down
6 changes: 3 additions & 3 deletions Request/Executor.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ public function execute(array $data, array $context = [])

$executionResult = GraphQL::executeAndReturnResult(
$this->schema,
isset($data['query']) ? $data['query'] : null,
isset($data[ParserInterface::PARAM_QUERY]) ? $data[ParserInterface::PARAM_QUERY] : null,
$context,
$context,
$data['variables'],
$data['operationName']
$data[ParserInterface::PARAM_VARIABLES],
isset($data[ParserInterface::PARAM_OPERATION_NAME]) ? $data[ParserInterface::PARAM_OPERATION_NAME] : null
);

if (null !== $this->errorHandler) {
Expand Down
6 changes: 6 additions & 0 deletions Resources/config/routing/graphql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ overblog_graphql_endpoint:
defaults:
_controller: OverblogGraphQLBundle:Graph:endpoint
_format: "json"

overblog_graphql_batch_endpoint:
path: /batch
defaults:
_controller: OverblogGraphQLBundle:Graph:batchEndpoint
_format: "json"
24 changes: 13 additions & 11 deletions Tests/Functional/Controller/GraphControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,20 +154,22 @@ public function testBatchEndpointAction()
$client = static::createClient(['test_case' => 'connection']);

$data = [
'friends' => [
[
'id' => 'friends',
'query' => $this->friendsQuery,
],
'friendsTotalCount' => [
[
'id' => 'friendsTotalCount',
'query' => $this->friendsTotalCountQuery,
],
];

$client->request('POST', '/?batch', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode($data));
$client->request('POST', '/batch', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode($data));
$result = $client->getResponse()->getContent();

$expected = [
'friends' => ['data' => $this->expectedData],
'friendsTotalCount' => ['data' => ['user' => ['friends' => ['totalCount' => 4]]]],
['id' => 'friends', 'payload' => ['data' => $this->expectedData]],
['id' => 'friendsTotalCount', 'payload' => ['data' => ['user' => ['friends' => ['totalCount' => 4]]]]],
];
$this->assertEquals($expected, json_decode($result, true), $result);
}
Expand All @@ -179,18 +181,18 @@ public function testBatchEndpointAction()
public function testBatchEndpointWithEmptyQuery()
{
$client = static::createClient();
$client->request('GET', '/?batch', [], [], ['CONTENT_TYPE' => 'application/json'], '{}');
$client->request('GET', '/batch', [], [], ['CONTENT_TYPE' => 'application/json'], '{}');
$client->getResponse()->getContent();
}

/**
* @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
* @expectedExceptionMessage Only request with content type " is accepted.
* @expectedExceptionMessage Only request with content type "application/json" is accepted.
*/
public function testBatchEndpointWrongContentType()
{
$client = static::createClient();
$client->request('GET', '/?batch');
$client->request('GET', '/batch');
$client->getResponse()->getContent();
}

Expand All @@ -201,18 +203,18 @@ public function testBatchEndpointWrongContentType()
public function testBatchEndpointWithInvalidJson()
{
$client = static::createClient();
$client->request('GET', '/?batch', [], [], ['CONTENT_TYPE' => 'application/json'], '{');
$client->request('GET', '/batch', [], [], ['CONTENT_TYPE' => 'application/json'], '{');
$client->getResponse()->getContent();
}

/**
* @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
* @expectedExceptionMessage No valid query found in node "test"
* @expectedExceptionMessage 1 is not a valid query
*/
public function testBatchEndpointWithInvalidQuery()
{
$client = static::createClient();
$client->request('GET', '/?batch', [], [], ['CONTENT_TYPE' => 'application/json'], '{"test" : {"query": 1}}');
$client->request('GET', '/batch', [], [], ['CONTENT_TYPE' => 'application/json'], '{"test" : {"query": 1}}');
$client->getResponse()->getContent();
}
}