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
6 changes: 3 additions & 3 deletions ExpressionLanguage/ConfigExpressionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function ($alias, $args = '[]') {
new ExpressionFunction(
'mutateAndGetPayloadCallback',
function ($mutateAndGetPayload) {
$code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $info) { ';
$code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $context, $info) { ';
$code .= 'return '.$mutateAndGetPayload.'; }';

return $code;
Expand All @@ -60,7 +60,7 @@ function ($mutateAndGetPayload) {
new ExpressionFunction(
'idFetcherCallback',
function ($idFetcher) {
$code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $info) { ';
$code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $context, $info) { ';
$code .= 'return '.$idFetcher.'; }';

return $code;
Expand All @@ -70,7 +70,7 @@ function ($idFetcher) {
new ExpressionFunction(
'resolveSingleInputCallback',
function ($resolveSingleInput) {
$code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $info) { ';
$code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $context, $info) { ';
$code .= 'return '.$resolveSingleInput.'; }';

return $code;
Expand Down
44 changes: 44 additions & 0 deletions Relay/Connection/Output/ConnectionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ public static function connectionFromArray($data, $args = [])
);
}

/**
* A version of `connectionFromArray` that takes a promised array, and returns a
* promised connection.
*
* @param mixed $dataPromise a promise
* @param array|Argument $args
*
* @return mixed a promise
*/
public static function connectionFromPromisedArray($dataPromise, $args = [])
{
self::checkPromise($dataPromise);

return $dataPromise->then(function ($data) use ($args) {
return static::connectionFromArray($data, $args);
});
}

/**
* Given a slice (subset) of an array, returns a connection object for use in
* GraphQL.
Expand Down Expand Up @@ -139,6 +157,25 @@ public static function connectionFromArraySlice($arraySlice, $args, array $meta)
);
}

/**
* A version of `connectionFromArraySlice` that takes a promised array slice,
* and returns a promised connection.
*
* @param mixed $dataPromise a promise
* @param array|Argument $args
* @param array $meta
*
* @return mixed a promise
*/
public static function connectionFromPromisedArraySlice($dataPromise, $args, array $meta)
{
self::checkPromise($dataPromise);

return $dataPromise->then(function ($arraySlice) use ($args, $meta) {
return static::connectionFromArraySlice($arraySlice, $args, $meta);
});
}

/**
* Return the cursor associated with an object in an array.
*
Expand Down Expand Up @@ -216,4 +253,11 @@ private static function getOptionsWithDefaults(array $options, array $defaults)
{
return $options + $defaults;
}

private static function checkPromise($value)
{
if (!is_callable([$value, 'then'])) {
throw new \InvalidArgumentException('This is not a valid promise.');
}
}
}
2 changes: 1 addition & 1 deletion Relay/Mutation/MutationFieldDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function toMappingDefinition(array $config)
'args' => [
'input' => ['type' => $inputType],
],
'resolve' => "@=resolver('relay_mutation_field', [args, mutateAndGetPayloadCallback($mutateAndGetPayload)])",
'resolve' => "@=resolver('relay_mutation_field', [args, context, info, mutateAndGetPayloadCallback($mutateAndGetPayload)])",
];
}

Expand Down
21 changes: 17 additions & 4 deletions Relay/Mutation/MutationFieldResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,31 @@

namespace Overblog\GraphQLBundle\Relay\Mutation;

use GraphQL\Executor\Promise\PromiseAdapter;
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Resolver\Resolver;

class MutationFieldResolver
{
public function resolve($args, \Closure $mutateAndGetPayloadCallback)
/**
* @var PromiseAdapter
*/
private $promiseAdapter;

public function __construct(PromiseAdapter $promiseAdapter)
{
$this->promiseAdapter = $promiseAdapter;
}

public function resolve($args, $context, $info, \Closure $mutateAndGetPayloadCallback)
{
$input = new Argument($args['input']);

$payload = $mutateAndGetPayloadCallback($input);
Resolver::setObjectOrArrayValue($payload, 'clientMutationId', $input['clientMutationId']);
return $this->promiseAdapter->createFulfilled($mutateAndGetPayloadCallback($input, $context, $info))
->then(function ($payload) use ($input) {
Resolver::setObjectOrArrayValue($payload, 'clientMutationId', $input['clientMutationId']);

return $payload;
return $payload;
});
}
}
2 changes: 1 addition & 1 deletion Relay/Node/NodeFieldDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function toMappingDefinition(array $config)
'args' => [
'id' => ['type' => 'ID!', 'description' => 'The ID of an object'],
],
'resolve' => "@=resolver('relay_node_field', [args, idFetcherCallback($idFetcher)])",
'resolve' => "@=resolver('relay_node_field', [args, context, info, idFetcherCallback($idFetcher)])",
];
}

Expand Down
4 changes: 2 additions & 2 deletions Relay/Node/NodeFieldResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

class NodeFieldResolver
{
public function resolve($args, \Closure $idFetcherCallback)
public function resolve($args, $context, $info, \Closure $idFetcherCallback)
{
return $idFetcherCallback($args['id']);
return $idFetcherCallback($args['id'], $context, $info);
}
}
2 changes: 1 addition & 1 deletion Relay/Node/PluralIdentifyingRootFieldDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function toMappingDefinition(array $config)
'type' => "[${config['outputType']}]",
'args' => [$argName => ['type' => "[${config['inputType']}!]!"]],
'resolve' => sprintf(
"@=resolver('relay_plural_identifying_field', [args['$argName'], resolveSingleInputCallback(%s)])",
"@=resolver('relay_plural_identifying_field', [args['$argName'], context, info, resolveSingleInputCallback(%s)])",
$this->cleanResolveSingleInput($config['resolveSingleInput'])
),
];
Expand Down
18 changes: 15 additions & 3 deletions Relay/Node/PluralIdentifyingRootFieldResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,28 @@

namespace Overblog\GraphQLBundle\Relay\Node;

use GraphQL\Executor\Promise\PromiseAdapter;

class PluralIdentifyingRootFieldResolver
{
public function resolve(array $inputs, callable $resolveSingleInput)
/**
* @var PromiseAdapter
*/
private $promiseAdapter;

public function __construct(PromiseAdapter $promiseAdapter)
{
$this->promiseAdapter = $promiseAdapter;
}

public function resolve(array $inputs, $context, $info, callable $resolveSingleInput)
{
$data = [];

foreach ($inputs as $input) {
$data[$input] = call_user_func_array($resolveSingleInput, [$input]);
$data[$input] = $this->promiseAdapter->createFulfilled(call_user_func_array($resolveSingleInput, [$input, $context, $info]));
}

return $data;
return $this->promiseAdapter->all($data);
}
}
4 changes: 4 additions & 0 deletions Resources/config/graphql_resolvers.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
services:
overblog_graphql.resolver.relay_mutation_field:
class: Overblog\GraphQLBundle\Relay\Mutation\MutationFieldResolver
arguments:
- "@overblog_graphql.promise_adapter"
tags:
- { name: overblog_graphql.resolver, alias: "relay_mutation_field", method: "resolve" }

Expand All @@ -16,5 +18,7 @@ services:

overblog_graphql.resolver.relay_plural_identifying_field:
class: Overblog\GraphQLBundle\Relay\Node\PluralIdentifyingRootFieldResolver
arguments:
- "@overblog_graphql.promise_adapter"
tags:
- { name: overblog_graphql.resolver, alias: "relay_plural_identifying_field", method: "resolve" }
40 changes: 40 additions & 0 deletions Tests/Functional/Relay/Mutation/MutationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,26 @@ public function testSupportsThunksAsInputAndOutputFields()
$this->assertGraphQL($query, $expectedData);
}

public function testSupportsPromiseMutations()
{
$query = <<<'EOF'
mutation M {
simplePromiseMutation(input: {clientMutationId: "abc"}) {
result
clientMutationId
}
}
EOF;
$expectedData = [
'simplePromiseMutation' => [
'result' => 1,
'clientMutationId' => 'abc',
],
];

$this->assertGraphQL($query, $expectedData);
}

public function testContainsCorrectInput()
{
$query = <<<'EOF'
Expand Down Expand Up @@ -240,6 +260,26 @@ public function testContainsCorrectField()
'kind' => 'OBJECT',
],
],
[
'name' => 'simplePromiseMutation',
'args' => [
[
'name' => 'input',
'type' => [
'name' => null,
'kind' => 'NON_NULL',
'ofType' => [
'name' => 'simplePromiseMutationInput',
'kind' => 'INPUT_OBJECT',
],
],
],
],
'type' => [
'name' => 'simplePromiseMutationPayload',
'kind' => 'OBJECT',
],
],
],
],
],
Expand Down
32 changes: 32 additions & 0 deletions Tests/Functional/app/Mutation/SimplePromiseMutation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the OverblogGraphQLBundle package.
*
* (c) Overblog <http://github.com/overblog/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Overblog\GraphQLBundle\Tests\Functional\app\Mutation;

use GraphQL\Executor\Promise\PromiseAdapter;

class SimplePromiseMutation
{
/**
* @var PromiseAdapter
*/
private $promiseAdapter;

public function __construct(PromiseAdapter $promiseAdapter)
{
$this->promiseAdapter = $promiseAdapter;
}

public function mutate()
{
return $this->promiseAdapter->createFulfilled(['result' => 1]);
}
}
5 changes: 5 additions & 0 deletions Tests/Functional/app/config/mutation/mapping/Inputs.types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ simpleMutationWithThunkFieldsInput:
config:
fields:
inputData : { type: "Int" }

simplePromiseMutationInput:
type: relay-mutation-input
config:
fields: []
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ simpleMutationWithThunkFieldsPayload:
config:
fields:
result: { type: "Int" }

simplePromiseMutationPayload:
type: relay-mutation-payload
config:
fields:
result: { type: "Int" }
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ RootMutation:
inputType: simpleMutationWithThunkFieldsInput
payloadType: simpleMutationWithThunkFieldsPayload
mutateAndGetPayload: "@=mutation('simple_mutation_with_thunk_fields', [value])"
simplePromiseMutation:
builder: "Relay::Mutation"
builderConfig:
inputType: simplePromiseMutationInput
payloadType: simplePromiseMutationPayload
mutateAndGetPayload: "@=mutation('simple_promise_mutation')"
9 changes: 8 additions & 1 deletion Tests/Functional/app/config/mutation/services.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
services:
overblog_graphql.test.mutation:
overblog_graphql.test.simple_mutation_with_thunk_fields:
class: Overblog\GraphQLBundle\Tests\Functional\app\Mutation\SimpleMutationWithThunkFieldsMutation
tags:
- { name: "overblog_graphql.mutation", alias: "simple_mutation_with_thunk_fields", method: "mutate" }

overblog_graphql.test.simple_promise_mutation:
class: Overblog\GraphQLBundle\Tests\Functional\app\Mutation\SimplePromiseMutation
arguments:
- "@overblog_graphql.react.promise_adapter"
tags:
- { name: "overblog_graphql.mutation", alias: "simple_promise_mutation", method: "mutate" }
44 changes: 44 additions & 0 deletions Tests/Relay/Connection/Output/AbstractConnectionBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* This file is part of the OverblogGraphQLBundle package.
*
* (c) Overblog <http://github.com/overblog/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Overblog\GraphQLBundle\Tests\Relay\Connection\Output;

use Overblog\GraphQLBundle\Relay\Connection\Output\Connection;
use Overblog\GraphQLBundle\Relay\Connection\Output\Edge;
use Overblog\GraphQLBundle\Relay\Connection\Output\PageInfo;

abstract class AbstractConnectionBuilderTest extends \PHPUnit_Framework_TestCase
{
protected $letters = ['A', 'B', 'C', 'D', 'E'];

protected function getExpectedConnection(array $wantedEdges, $hasPreviousPage, $hasNextPage)
{
$edges = [
'A' => new Edge('YXJyYXljb25uZWN0aW9uOjA=', 'A'),
'B' => new Edge('YXJyYXljb25uZWN0aW9uOjE=', 'B'),
'C' => new Edge('YXJyYXljb25uZWN0aW9uOjI=', 'C'),
'D' => new Edge('YXJyYXljb25uZWN0aW9uOjM=', 'D'),
'E' => new Edge('YXJyYXljb25uZWN0aW9uOjQ=', 'E'),
];

$expectedEdges = array_values(array_intersect_key($edges, array_flip($wantedEdges)));

return new Connection(
$expectedEdges,
new PageInfo(
isset($expectedEdges[0]) ? $expectedEdges[0]->cursor : null,
end($expectedEdges) ? end($expectedEdges)->cursor : null,
$hasPreviousPage,
$hasNextPage
)
);
}
}
Loading