Test Failed
Pull Request — master (#33)
by Mathieu
05:55
created

HandlesGraphqlRequests::typeFromBaseClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 2
rs 10
1
<?php
2
3
namespace Butler\Graphql\Concerns;
4
5
use Butler\Graphql\DataLoader;
6
use Exception;
7
use GraphQL\Error\Debug;
8
use GraphQL\Error\Error as GraphqlError;
9
use GraphQL\Error\FormattedError;
10
use GraphQL\Executor\Promise\PromiseAdapter;
11
use GraphQL\GraphQL;
12
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
13
use GraphQL\Language\AST\TypeDefinitionNode;
14
use GraphQL\Language\AST\UnionTypeDefinitionNode;
15
use GraphQL\Type\Definition\ResolveInfo;
16
use GraphQL\Utils\BuildSchema;
17
use Illuminate\Contracts\Debug\ExceptionHandler;
18
use Illuminate\Database\Eloquent\ModelNotFoundException;
19
use Illuminate\Http\Request;
20
use Illuminate\Support\Str;
21
use Illuminate\Validation\ValidationException;
22
23
trait HandlesGraphqlRequests
24
{
25
    /**
26
     * Invoke the Graphql request handler.
27
     *
28
     * @param  \Illuminate\Http\Request  $request
29
     * @return array
30
     */
31 23
    public function __invoke(Request $request)
32
    {
33 23
        $loader = app(DataLoader::class);
34 23
        $schema = BuildSchema::build($this->getSchema(), [$this, 'decorateTypeConfig']);
35 23
        $result = null;
36
37 23
        GraphQL::useExperimentalExecutor();
38
39 23
        GraphQL::promiseToExecute(
40 23
            app(PromiseAdapter::class),
41
            $schema,
42 23
            $request->input('query'),
43 23
            null, // root
44 23
            compact('loader'), // context
45 23
            $request->input('variables'),
46 23
            $request->input('operationName'),
47 23
            [$this, 'resolveField'],
48 23
            null // validationRules
49
        )->then(function ($value) use (&$result) {
50 23
            $result = $value;
51 23
        });
52
53 23
        $loader->run();
54
55 23
        $result->setErrorFormatter([$this, 'errorFormatter']);
56
57 23
        return $this->decorateResponse($result->toArray($this->debugFlags()));
58
    }
59
    
60 9
    public function getSchema()
61
    {
62 9
        return file_get_contents($this->schemaPath())
63 9
    }
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected '}', expecting ';' on line 63 at column 4
Loading history...
64
65 9
    public function errorFormatter(GraphqlError $graphqlError)
66 9
    {
67 7
        $formattedError = FormattedError::createFromException($graphqlError);
68 9
        $throwable = $graphqlError->getPrevious();
69
70
        $this->reportException(
71 9
            $throwable instanceof Exception
72 1
            ? $throwable
73 1
            : $graphqlError
74
        );
75
76
        if ($throwable instanceof ModelNotFoundException) {
77
            return array_merge($formattedError, [
78
                'message' => class_basename($throwable->getModel()) . ' not found.',
79
                'extensions' => [
80 8
                    'category' => 'client',
81 1
                ],
82 1
            ]);
83
        }
84 1
85 1
        if ($throwable instanceof ValidationException) {
86
            return array_merge($formattedError, [
87
                'message' => $throwable->getMessage(),
88
                'extensions' => [
89
                    'category' => 'validation',
90 7
                    'validation' => $throwable->errors(),
91
                ],
92
            ]);
93 9
        }
94
95 9
        return $formattedError;
96 9
    }
97
98 23
    public function reportException(Exception $exception)
99
    {
100 23
        app(ExceptionHandler::class)->report($exception);
101
    }
102
103 23
    public function schemaPath()
104
    {
105 23
        return config('butler.graphql.schema');
106 21
    }
107
108 23
    public function decorateTypeConfig(array $config, TypeDefinitionNode $typeDefinitionNode)
109
    {
110
        if ($this->shouldDecorateWithResolveType($typeDefinitionNode)) {
111 23
            $config['resolveType'] = [$this, 'resolveType'];
112
        }
113 23
        return $config;
114 23
    }
115
116
    protected function shouldDecorateWithResolveType(TypeDefinitionNode $typeDefinitionNode)
117 23
    {
118
        return $typeDefinitionNode instanceof InterfaceTypeDefinitionNode
119 23
            || $typeDefinitionNode instanceof UnionTypeDefinitionNode;
120 23
    }
121 7
122
    public function debugFlags()
123 23
    {
124 3
        $flags = 0;
125
        if (config('butler.graphql.include_debug_message')) {
126 23
            $flags |= Debug::INCLUDE_DEBUG_MESSAGE;
127
        }
128
        if (config('butler.graphql.include_trace')) {
129 22
            $flags |= Debug::INCLUDE_TRACE;
130
        }
131 22
        return $flags;
132 10
    }
133 14
134
    public function resolveField($source, $args, $context, ResolveInfo $info)
135 14
    {
136 1
        $field = $this->fieldFromResolver($source, $args, $context, $info)
137 14
            ?? $this->fieldFromArray($source, $args, $context, $info)
138
            ?? $this->fieldFromObject($source, $args, $context, $info);
139
140 4
        return $field instanceof \Closure
141
            ? $field($source, $args, $context, $info)
142 4
            : $field;
143 4
    }
144 4
145 4
    public function resolveType($source, $context, ResolveInfo $info)
146
    {
147
        return $this->typeFromArray($source, $context, $info)
148 22
            ?? $this->typeFromObject($source, $context, $info)
149
            ?? $this->typeFromParentResolver($source, $context, $info)
150 22
            ?? $this->typeFromBaseClass($source, $context, $info);
151 22
    }
152
153 22
    public function fieldFromResolver($source, $args, $context, ResolveInfo $info)
154 22
    {
155 21
        $className = $this->resolveClassName($info);
156 21
        $methodName = $this->resolveFieldMethodName($info);
157
158
        if (app()->has($className) || class_exists($className)) {
159 10
            $resolver = app($className);
160
            if (method_exists($resolver, $methodName)) {
161 10
                return $resolver->{$methodName}($source, $args, $context, $info);
162
            }
163 10
        }
164 8
    }
165
166 8
    public function fieldFromArray($source, $args, $context, ResolveInfo $info)
167 8
    {
168
        if (is_array($source) || $source instanceof \ArrayAccess) {
169 8
            return collect($this->propertyNames($info))
170 8
                ->map(function ($propertyName) use ($source) {
171 8
                    return $source[$propertyName] ?? null;
172
                })
173 7
                ->reject(function ($value) {
174
                    return is_null($value);
175 7
                })
176
                ->first();
177 7
        }
178 7
    }
179
180 7
    public function fieldFromObject($source, $args, $context, ResolveInfo $info)
181 7
    {
182
        if (is_object($source)) {
183 7
            return collect($this->propertyNames($info))
184 7
                ->map(function ($propertyName) use ($source) {
185 7
                    return $source->{$propertyName} ?? null;
186
                })
187 1
                ->reject(function ($value) {
188
                    return is_null($value);
189 4
                })
190
                ->first();
191 4
        }
192 4
    }
193
194 2
    public function typeFromArray($source, $context, ResolveInfo $info)
195
    {
196 4
        if (is_array($source) || $source instanceof \ArrayAccess) {
197
            return $source['__typename'] ?? null;
198 4
        }
199 2
    }
200
201 4
    public function typeFromObject($source, $context, ResolveInfo $info)
202
    {
203 4
        if (is_object($source)) {
204
            return $source->__typename ?? null;
205 4
        }
206 4
    }
207
208 4
    public function typeFromParentResolver($source, $context, ResolveInfo $info)
209 4
    {
210 4
        $className = $this->resolveClassName($info);
211 4
        $methodName = $this->resolveTypeMethodName($info);
212
213
        if (app()->has($className) || class_exists($className)) {
214
            $resolver = app($className);
215
            if (method_exists($resolver, $methodName)) {
216 1
                return $resolver->{$methodName}($source, $context, $info);
217
            }
218 1
        }
219 1
    }
220
221
    public function typeFromBaseClass($source, $context, ResolveInfo $info)
222
    {
223 10
        if (is_object($source)) {
224
            return class_basename($source);
225 10
        }
226 10
    }
227 10
228 10
    public function propertyNames(ResolveInfo $info): array
229 10
    {
230
        return collect([
231
            Str::snake($info->fieldName),
232 22
            Str::camel($info->fieldName),
233
            Str::kebab(Str::camel($info->fieldName)),
234 22
        ])->unique()->toArray();
235 21
    }
236
237
    protected function resolveClassName(ResolveInfo $info): string
238 11
    {
239 1
        if ($info->parentType->name === 'Query') {
240
            return $this->queriesNamespace() . Str::studly($info->fieldName);
241
        }
242 11
243
        if ($info->parentType->name === 'Mutation') {
244
            return $this->mutationsNamespace() . Str::studly($info->fieldName);
245 22
        }
246
247 22
        return $this->typesNamespace() . Str::studly($info->parentType->name);
248 22
    }
249
250
    public function resolveFieldMethodName(ResolveInfo $info): string
251 11
    {
252
        if (in_array($info->parentType->name, ['Query', 'Mutation'])) {
253
            return '__invoke';
254 4
        }
255
256 4
        return Str::camel($info->fieldName);
257 2
    }
258
259
    public function resolveTypeMethodName(ResolveInfo $info): string
260 2
    {
261
        if (in_array($info->parentType->name, ['Query', 'Mutation'])) {
262
            return 'resolveType';
263 22
        }
264
265 22
        return 'resolveTypeFor' . ucfirst(Str::camel($info->fieldName));
266
    }
267
268 21
    public function namespace(): string
269
    {
270 21
        return config('butler.graphql.namespace');
271
    }
272
273 1
    public function queriesNamespace(): string
274
    {
275 1
        return $this->namespace() . 'Queries\\';
276
    }
277
278 11
    public function mutationsNamespace(): string
279
    {
280 11
        return $this->namespace() . 'Mutations\\';
281
    }
282
283 23
    public function typesNamespace(): string
284
    {
285 23
        return $this->namespace() . 'Types\\';
286 1
    }
287
288 23
    public function decorateResponse(array $data): array
289
    {
290
        if (app()->bound('debugbar') && app('debugbar')->isEnabled()) {
291
            $data['debug'] = app('debugbar')->getData();
292
        }
293
        return $data;
294
    }
295
}
296