Passed
Pull Request — master (#321)
by Christoffer
02:36
created

Execution   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 74
dl 0
loc 170
rs 10
c 0
b 0
f 0
wmc 23

3 Methods

Rating   Name   Duplication   Size   Complexity  
A executeOperation() 0 31 5
B execute() 0 47 6
C createContext() 0 64 12
1
<?php
2
3
namespace Digia\GraphQL\Execution;
4
5
use Digia\GraphQL\Error\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
16
class Execution implements ExecutionInterface
17
{
18
    /**
19
     * @inheritdoc
20
     */
21
    public function execute(
22
        Schema $schema,
23
        DocumentNode $documentNode,
24
        $rootValue = null,
25
        $contextValue = null,
26
        array $variableValues = [],
27
        ?string $operationName = null,
28
        ?callable $fieldResolver = null,
29
        ?ErrorHandlerInterface $errorHandler = null
30
    ): ExecutionResult {
31
        try {
32
            $context = $this->createContext(
33
                $schema,
34
                $documentNode,
35
                $rootValue,
36
                $contextValue,
37
                $variableValues,
38
                $operationName,
39
                $fieldResolver
40
            );
41
42
            // Return early errors if execution context failed.
43
            if (!empty($context->getErrors())) {
44
                return new ExecutionResult(null, $context->getErrors());
45
            }
46
        } catch (ExecutionException $error) {
47
            return new ExecutionResult(null, [$error]);
48
        }
49
50
        $valuesResolver = new ValuesResolver();
51
        $fieldCollector = new FieldCollector($context, $valuesResolver);
52
53
        $data = $this->executeOperation($operationName, $context, $fieldCollector, $valuesResolver);
54
55
        if (null !== $errorHandler) {
56
            foreach ($context->getErrors() as $error) {
57
                $errorHandler->handleError($error);
58
            }
59
        }
60
61
        if ($data instanceof PromiseInterface) {
62
            $data->then(function ($resolvedData) use (&$data) {
63
                $data = $resolvedData;
64
            });
65
        }
66
67
        return new ExecutionResult($data, $context->getErrors());
68
    }
69
70
    /**
71
     * @param null|string                $operationName
72
     * @param ExecutionContext           $context
73
     * @param FieldCollector             $fieldCollector
74
     * @param ValuesResolver             $valuesResolver
75
     * @param ErrorHandlerInterface|null $errorHandler
76
     * @return array|mixed|null|PromiseInterface
77
     */
78
    protected function executeOperation(
79
        ?string $operationName,
80
        ExecutionContext $context,
81
        FieldCollector $fieldCollector,
82
        ValuesResolver $valuesResolver,
83
        ?ErrorHandlerInterface $errorHandler = null
0 ignored issues
show
Unused Code introduced by
The parameter $errorHandler is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

83
        /** @scrutinizer ignore-unused */ ?ErrorHandlerInterface $errorHandler = null

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84
    ) {
85
        $strategy = $operationName === 'mutation'
86
            ? new SerialExecutionStrategy($context, $fieldCollector, $valuesResolver)
87
            : new ParallelExecutionStrategy($context, $fieldCollector, $valuesResolver);
88
89
        $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
90
91
        try {
92
            $result = $strategy->execute();
93
        } catch (ExecutionException $exception) {
94
            $context->addError($exception);
95
        } catch (\Throwable $exception) {
96
            $context->addError(
97
                new ExecutionException($exception->getMessage(), null, null, null, null, null, $exception)
98
            );
99
        }
100
101
        if ($result instanceof PromiseInterface) {
102
            return $result->then(null, function (ExecutionException $exception) use ($context) {
103
                $context->addError($exception);
104
                return \React\Promise\resolve(null);
105
            });
106
        }
107
108
        return $result;
109
    }
110
111
    /**
112
     * @param Schema        $schema
113
     * @param DocumentNode  $documentNode
114
     * @param mixed         $rootValue
115
     * @param mixed         $contextValue
116
     * @param mixed         $rawVariableValues
117
     * @param null|string   $operationName
118
     * @param callable|null $fieldResolver
119
     * @return ExecutionContext
120
     * @throws ExecutionException
121
     */
122
    protected function createContext(
123
        Schema $schema,
124
        DocumentNode $documentNode,
125
        $rootValue,
126
        $contextValue,
127
        $rawVariableValues,
128
        ?string $operationName = null,
129
        ?callable $fieldResolver = null
130
    ): ExecutionContext {
131
        $errors    = [];
132
        $fragments = [];
133
        $operation = null;
134
135
        foreach ($documentNode->getDefinitions() as $definition) {
136
            if ($definition instanceof OperationDefinitionNode) {
137
                if (null === $operationName && null !== $operation) {
138
                    throw new ExecutionException(
139
                        'Must provide operation name if query contains multiple operations.'
140
                    );
141
                }
142
143
                if (null === $operationName || $definition->getNameValue() === $operationName) {
144
                    $operation = $definition;
145
                }
146
147
                continue;
148
            }
149
150
            if ($definition instanceof FragmentDefinitionNode || $definition instanceof FragmentSpreadNode) {
151
                $fragments[$definition->getNameValue()] = $definition;
152
153
                continue;
154
            }
155
        }
156
157
        if (null === $operation) {
158
            if (null !== $operationName) {
159
                throw new ExecutionException(sprintf('Unknown operation named "%s".', $operationName));
160
            }
161
162
            throw new ExecutionException('Must provide an operation.');
163
        }
164
165
        $coercedVariableValues = (new ValuesResolver())->coerceVariableValues(
166
            $schema,
167
            $operation->getVariableDefinitions(),
168
            $rawVariableValues
169
        );
170
171
        $variableValues = $coercedVariableValues->getValue();
172
173
        if ($coercedVariableValues->hasErrors()) {
174
            $errors = $coercedVariableValues->getErrors();
175
        }
176
177
        return new ExecutionContext(
178
            $schema,
179
            $fragments,
180
            $rootValue,
181
            $contextValue,
182
            $variableValues,
183
            $fieldResolver,
184
            $operation,
185
            $errors
186
        );
187
    }
188
}
189