Passed
Pull Request — master (#345)
by
unknown
03:45
created

Execution   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 220
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 96
dl 0
loc 220
rs 10
c 0
b 0
f 0
wmc 29

4 Methods

Rating   Name   Duplication   Size   Complexity  
A executeOperation() 0 30 5
B execute() 0 47 6
C createContext() 0 64 12
B promiseExecute() 0 47 6
1
<?php
2
3
namespace Digia\GraphQL\Execution;
4
5
use Digia\GraphQL\Error\Handler\ErrorHandlerInterface;
6
use Digia\GraphQL\Execution\Strategy\FieldCollector;
7
use Digia\GraphQL\Execution\Strategy\ParallelExecutionStrategy;
8
use Digia\GraphQL\Execution\Strategy\SerialExecutionStrategy;
9
use Digia\GraphQL\Language\Node\DocumentNode;
10
use Digia\GraphQL\Language\Node\FragmentDefinitionNode;
11
use Digia\GraphQL\Language\Node\FragmentSpreadNode;
12
use Digia\GraphQL\Language\Node\OperationDefinitionNode;
13
use Digia\GraphQL\Schema\Schema;
14
use React\Promise\PromiseInterface;
15
use function React\Promise\resolve;
16
17
class Execution implements ExecutionInterface, PromisedExecutionInterface
18
{
19
    /**
20
     * @inheritdoc
21
     */
22
    public function execute(
23
        Schema $schema,
24
        DocumentNode $documentNode,
25
        $rootValue = null,
26
        $contextValue = null,
27
        array $variableValues = [],
28
        ?string $operationName = null,
29
        ?callable $fieldResolver = null,
30
        ?ErrorHandlerInterface $errorHandler = null
31
    ): ExecutionResult {
32
        try {
33
            $context = $this->createContext(
34
                $schema,
35
                $documentNode,
36
                $rootValue,
37
                $contextValue,
38
                $variableValues,
39
                $operationName,
40
                $fieldResolver
41
            );
42
43
            // Return early errors if execution context failed.
44
            if (!empty($context->getErrors())) {
45
                return new ExecutionResult(null, $context->getErrors());
46
            }
47
        } catch (ExecutionException $error) {
48
            return new ExecutionResult(null, [$error]);
49
        }
50
51
        $valuesResolver = new ValuesResolver();
52
        $fieldCollector = new FieldCollector($context, $valuesResolver);
53
54
        $data = $this->executeOperation($operationName, $context, $fieldCollector, $valuesResolver);
55
56
        if ($data instanceof PromiseInterface) {
57
            $data->then(function ($resolvedData) use (&$data) {
58
                $data = $resolvedData;
59
            });
60
        }
61
62
        if (null !== $errorHandler) {
63
            foreach ($context->getErrors() as $error) {
64
                $errorHandler->handleExecutionError($error, $context);
65
            }
66
        }
67
68
        return new ExecutionResult($data, $context->getErrors());
69
    }
70
71
    /**
72
     * @inheritdoc
73
     */
74
    public function promiseExecute(
75
        Schema $schema,
76
        DocumentNode $documentNode,
77
        $rootValue = null,
78
        $contextValue = null,
79
        array $variableValues = [],
80
        ?string $operationName = null,
81
        ?callable $fieldResolver = null,
82
        ?ErrorHandlerInterface $errorHandler = null
83
    ): PromiseInterface {
84
        try {
85
            $context = $this->createContext(
86
                $schema,
87
                $documentNode,
88
                $rootValue,
89
                $contextValue,
90
                $variableValues,
91
                $operationName,
92
                $fieldResolver
93
            );
94
95
            // Return early errors if execution context failed.
96
            if (!empty($context->getErrors())) {
97
                return resolve(new ExecutionResult(null, $context->getErrors()));
98
            }
99
        } catch (ExecutionException $error) {
100
            return resolve(new ExecutionResult(null, [$error]));
101
        }
102
103
        $valuesResolver = new ValuesResolver();
104
        $fieldCollector = new FieldCollector($context, $valuesResolver);
105
106
        $data = $this->executeOperation($operationName, $context, $fieldCollector, $valuesResolver);
107
108
        if ($data instanceof PromiseInterface) {
109
            return $data->then(function ($resolvedData) use ($context) {
110
                return new ExecutionResult($resolvedData, $context->getErrors());
111
            });
112
        }
113
114
        if (null !== $errorHandler) {
115
            foreach ($context->getErrors() as $error) {
116
                $errorHandler->handleExecutionError($error, $context);
117
            }
118
        }
119
120
        return resolve(new ExecutionResult($data, $context->getErrors()));
121
    }
122
123
    /**
124
     * @param null|string      $operationName
125
     * @param ExecutionContext $context
126
     * @param FieldCollector   $fieldCollector
127
     * @param ValuesResolver   $valuesResolver
128
     * @return array|mixed|null|PromiseInterface
129
     */
130
    protected function executeOperation(
131
        ?string $operationName,
132
        ExecutionContext $context,
133
        FieldCollector $fieldCollector,
134
        ValuesResolver $valuesResolver
135
    ) {
136
        $strategy = $operationName === 'mutation'
137
            ? new SerialExecutionStrategy($context, $fieldCollector, $valuesResolver)
138
            : new ParallelExecutionStrategy($context, $fieldCollector, $valuesResolver);
139
140
        $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
141
142
        try {
143
            $result = $strategy->execute();
144
        } catch (ExecutionException $exception) {
145
            $context->addError($exception);
146
        } catch (\Throwable $exception) {
147
            $context->addError(
148
                new ExecutionException($exception->getMessage(), null, null, null, null, null, $exception)
149
            );
150
        }
151
152
        if ($result instanceof PromiseInterface) {
153
            return $result->then(null, function (ExecutionException $exception) use ($context) {
154
                $context->addError($exception);
155
                return \React\Promise\resolve(null);
156
            });
157
        }
158
159
        return $result;
160
    }
161
162
    /**
163
     * @param Schema        $schema
164
     * @param DocumentNode  $documentNode
165
     * @param mixed         $rootValue
166
     * @param mixed         $contextValue
167
     * @param mixed         $rawVariableValues
168
     * @param null|string   $operationName
169
     * @param callable|null $fieldResolver
170
     * @return ExecutionContext
171
     * @throws ExecutionException
172
     */
173
    protected function createContext(
174
        Schema $schema,
175
        DocumentNode $documentNode,
176
        $rootValue,
177
        $contextValue,
178
        $rawVariableValues,
179
        ?string $operationName = null,
180
        ?callable $fieldResolver = null
181
    ): ExecutionContext {
182
        $errors    = [];
183
        $fragments = [];
184
        $operation = null;
185
186
        foreach ($documentNode->getDefinitions() as $definition) {
187
            if ($definition instanceof OperationDefinitionNode) {
188
                if (null === $operationName && null !== $operation) {
189
                    throw new ExecutionException(
190
                        'Must provide operation name if query contains multiple operations.'
191
                    );
192
                }
193
194
                if (null === $operationName || $definition->getNameValue() === $operationName) {
195
                    $operation = $definition;
196
                }
197
198
                continue;
199
            }
200
201
            if ($definition instanceof FragmentDefinitionNode || $definition instanceof FragmentSpreadNode) {
202
                $fragments[$definition->getNameValue()] = $definition;
203
204
                continue;
205
            }
206
        }
207
208
        if (null === $operation) {
209
            if (null !== $operationName) {
210
                throw new ExecutionException(sprintf('Unknown operation named "%s".', $operationName));
211
            }
212
213
            throw new ExecutionException('Must provide an operation.');
214
        }
215
216
        $coercedVariableValues = (new ValuesResolver())->coerceVariableValues(
217
            $schema,
218
            $operation->getVariableDefinitions(),
219
            $rawVariableValues
220
        );
221
222
        $variableValues = $coercedVariableValues->getValue();
223
224
        if ($coercedVariableValues->hasErrors()) {
225
            $errors = $coercedVariableValues->getErrors();
226
        }
227
228
        return new ExecutionContext(
229
            $schema,
230
            $fragments,
231
            $rootValue,
232
            $contextValue,
233
            $variableValues,
234
            $fieldResolver,
235
            $operation,
236
            $errors
237
        );
238
    }
239
}
240