Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Test Failed
Pull Request — master (#4)
by Jérémiah
12:13
created

ConfigResolver::setDefaultResolveFn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
/*
4
 * This file is part of the OverblogGraphQLBundle package.
5
 *
6
 * (c) Overblog <http://github.com/overblog/>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Overblog\GraphQLBundle\Resolver;
13
14
use GraphQL\Executor\Executor;
15
use GraphQL\Type\Definition\ResolveInfo;
16
use Overblog\GraphQLBundle\Definition\Argument;
17
use Overblog\GraphQLBundle\Definition\ArgsInterface;
18
use Overblog\GraphQLBundle\Definition\FieldInterface;
19
use Overblog\GraphQLBundle\Error\UserError;
20
use Overblog\GraphQLBundle\Relay\Connection\Output\Connection;
21
use Overblog\GraphQLBundle\Relay\Connection\Output\Edge;
22
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
23
use Symfony\Component\OptionsResolver\OptionsResolver;
24
25
class ConfigResolver implements ResolverInterface
26
{
27
    /**
28
     * @var ExpressionLanguage
29
     */
30
    private $expressionLanguage;
31
32
    /**
33
     * @var TypeResolver
34
     */
35
    private $typeResolver;
36
37
    /**
38
     * @var FieldResolver
39
     */
40
    private $fieldResolver;
41
42
    /**
43
     * @var ArgResolver
44
     */
45
    private $argResolver;
46
47
    /**
48
     * @var callable
49
     */
50
    private $defaultResolveFn = ['GraphQL\Executor\Executor', 'defaultResolveFn'];
51
52 32
    /**
53
     * @var array
54
     *            [name => callable]
55
     */
56
    private $resolverMap = [];
57
58
    public function __construct(
59 32
        ResolverInterface $typeResolver,
60 32
        ResolverInterface $fieldResolver,
61 32
        ResolverInterface $argResolver,
62 32
        ExpressionLanguage $expressionLanguage
63 32
    ) {
64 32
        $this->typeResolver = $typeResolver;
0 ignored issues
show
Documentation Bug introduced by
$typeResolver is of type object<Overblog\GraphQLB...lver\ResolverInterface>, but the property $typeResolver was declared to be of type object<Overblog\GraphQLB...\Resolver\TypeResolver>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
65 32
        $this->fieldResolver = $fieldResolver;
0 ignored issues
show
Documentation Bug introduced by
$fieldResolver is of type object<Overblog\GraphQLB...lver\ResolverInterface>, but the property $fieldResolver was declared to be of type object<Overblog\GraphQLB...Resolver\FieldResolver>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
66 32
        $this->argResolver = $argResolver;
0 ignored issues
show
Documentation Bug introduced by
$argResolver is of type object<Overblog\GraphQLB...lver\ResolverInterface>, but the property $argResolver was declared to be of type object<Overblog\GraphQLB...e\Resolver\ArgResolver>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
67 32
        $this->expressionLanguage = $expressionLanguage;
68 32
        $this->resolverMap = [
69 32
            'fields' => [$this, 'resolveFields'],
70 32
            'isTypeOf' => [$this, 'resolveResolveCallback'],
71 32
            'interfaces' => [$this, 'resolveInterfaces'],
72 32
            'types' => [$this, 'resolveTypes'],
73 32
            'values' => [$this, 'resolveValues'],
74 32
            'resolveType' => [$this, 'resolveResolveCallback'],
75 32
            'resolveCursor' => [$this, 'resolveResolveCallback'],
76 32
            'resolveNode' => [$this, 'resolveResolveCallback'],
77 32
            'nodeType' => [$this, 'resolveTypeCallback'],
78 32
            'connectionFields' => [$this, 'resolveFields'],
79 32
            'edgeFields' => [$this, 'resolveFields'],
80 32
            'mutateAndGetPayload' => [$this, 'resolveResolveCallback'],
81 32
            'idFetcher' => [$this, 'resolveResolveCallback'],
82
            'nodeInterfaceType' => [$this, 'resolveTypeCallback'],
83 32
            'inputType' => [$this, 'resolveTypeCallback'],
84
            'outputType' => [$this, 'resolveTypeCallback'],
85
            'payloadType' => [$this, 'resolveTypeCallback'],
86
            'resolveSingleInput' => [$this, 'resolveResolveCallback'],
87
        ];
88
    }
89
90 27
    public function setDefaultResolveFn(callable $defaultResolveFn)
91
    {
92 27
        Executor::setDefaultResolveFn($defaultResolveFn);
93 1
94
        $this->defaultResolveFn = $defaultResolveFn;
95
    }
96 26
97 26
    public function addResolverMap($name, callable $resolver)
98 25
    {
99
        $this->resolverMap[$name] = $resolver;
100 26
    }
101 26
102
    public function resolve($config)
103 26
    {
104
        if (!is_array($config) || $config instanceof \ArrayAccess) {
105
            throw new \RuntimeException('Config must be an array or implement \ArrayAccess interface');
106 25
        }
107
108 25
        foreach ($config as $name => &$values) {
109 25
            if (!isset($this->resolverMap[$name]) || empty($values)) {
110 19
                continue;
111
            }
112 19
            $values = call_user_func_array($this->resolverMap[$name], [$values]);
113 19
        }
114 19
115 19
        return $config;
116
    }
117
118 19
    private function resolveFields(array $fields)
119 19
    {
120 19
        foreach ($fields as $field => &$options) {
121
            if (isset($options['builder']) && is_string($options['builder'])) {
122 19
                $alias = $options['builder'];
123
124 19
                $fieldBuilder = $this->fieldResolver->resolve($alias);
125 19
                $builderConfig = [];
126 19
                if (isset($options['builderConfig'])) {
127
                    if (!is_array($options['builderConfig'])) {
128
                        $options['builderConfig'] = [$options['builderConfig']];
129
                    }
130
                    $builderConfig = $this->resolve($options['builderConfig']);
131
                }
132
                $builderConfig['name'] = $field;
133
134 19
                $access = isset($options['access']) ? $options['access'] : null;
135 19
136
                if ($fieldBuilder instanceof FieldInterface) {
137 19
                    $options = $fieldBuilder->toFieldDefinition($builderConfig);
138
                } elseif (is_callable($fieldBuilder)) {
139 19
                    $options = call_user_func_array($fieldBuilder, [$builderConfig]);
140
                } elseif (is_object($fieldBuilder)) {
141
                    $options = get_object_vars($fieldBuilder);
142 25
                } else {
143 25
                    throw new \RuntimeException(sprintf('Could not build field "%s".', $alias));
144 25
                }
145
146 25
                $options['access'] = $access;
147
                $options = $this->resolveResolveAndAccessIfNeeded($options);
148
149
                unset($options['builderConfig'], $options['builder']);
150
151
                continue;
152
            }
153
154
            if (isset($options['type'])) {
155 25
                $options['type'] = $this->resolveTypeCallback($options['type']);
156 6
            }
157
158 6
            if (isset($options['args'])) {
159 6
                foreach ($options['args'] as &$argsOptions) {
160 6
                    $argsOptions['type'] = $this->resolveTypeCallback($argsOptions['type']);
161
                    if (isset($argsOptions['defaultValue'])) {
162
                        $argsOptions['defaultValue'] = $this->resolveUsingExpressionLanguageIfNeeded($argsOptions['defaultValue']);
163
                    }
164
                }
165
            }
166
167 6
            if (isset($options['argsBuilder'])) {
168
                $alias = $options['argsBuilder']['name'];
169 6
170 6
                $argsBuilder = $this->argResolver->resolve($alias);
171 6
                $argsBuilderConfig = [];
172
                if (isset($options['argsBuilder']['config'])) {
173
                    if (!is_array($options['argsBuilder']['config'])) {
174
                        $options['argsBuilder']['config'] = [$options['argsBuilder']['config']];
175
                    }
176
                    $argsBuilderConfig = $this->resolve($options['argsBuilder']['config']);
177
                }
178
179 6
                $options['args'] = isset($options['args']) ? $options['args'] : [];
180 6
181
                if ($argsBuilder instanceof ArgsInterface) {
182 25
                    $options['args'] = array_merge($argsBuilder->toArgsDefinition($argsBuilderConfig), $options['args']);
183
                } elseif (is_callable($argsBuilder)) {
184 25
                    $options['args'] = array_merge(call_user_func_array($argsBuilder, [$argsBuilderConfig]), $options['args']);
185
                } elseif (is_object($argsBuilder)) {
186
                    $options['args'] = array_merge(get_object_vars($argsBuilder), $options['args']);
187 25
                } else {
188
                    throw new \RuntimeException(sprintf('Could not build args "%s".', $alias));
189 25
                }
190
191
                unset($options['argsBuilder']);
192 25
            }
193
194 25
            $options = $this->resolveResolveAndAccessIfNeeded($options);
195
196 25
            if (isset($options['deprecationReason'])) {
197 25
                $options['deprecationReason'] = $this->resolveUsingExpressionLanguageIfNeeded($options['deprecationReason']);
198 25
            }
199
        }
200 25
201
        return $fields;
202
    }
203
204
    private function resolveResolveAndAccessIfNeeded(array $options)
205
    {
206
        $treatedOptions = $options;
207
208
        if (isset($treatedOptions['resolve'])) {
209 25
            $treatedOptions['resolve'] = $this->resolveResolveCallback($treatedOptions['resolve']);
210
        }
211 25
212
        if (isset($treatedOptions['access'])) {
213
            $resolveCallback = $this->defaultResolveFn;
214 25
215
            if (isset($treatedOptions['resolve'])) {
216
                $resolveCallback = $treatedOptions['resolve'];
217 25
            }
218 25
219
            $treatedOptions['resolve'] = $this->resolveAccessAndWrapResolveCallback($treatedOptions['access'], $resolveCallback);
220
        }
221 11
        unset($treatedOptions['access']);
222
223 11
        return $treatedOptions;
224
    }
225
226 11
    private function resolveTypeCallback($expr)
227
    {
228 11
        return function () use ($expr) {
229
            return $this->resolveType($expr);
230 11
        };
231 11
    }
232 11
233
    private function resolveInterfaces(array $rawInterfaces)
234 11
    {
235
        return $this->resolveTypes($rawInterfaces, 'GraphQL\\Type\\Definition\\InterfaceType');
236
    }
237 25
238
    private function resolveTypes(array $rawTypes, $parentClass = 'GraphQL\\Type\\Definition\\Type')
239 25
    {
240
        $types = [];
241 25
242
        foreach ($rawTypes as $alias) {
243
            $types[] = $this->resolveType($alias, $parentClass);
244
        }
245
246
        return $types;
247 25
    }
248
249
    private function resolveType($expr, $parentClass = 'GraphQL\\Type\\Definition\\Type')
250 5
    {
251
        $type = $this->typeResolver->resolve($expr);
252
253 5
        if (class_exists($parentClass) && !$type instanceof $parentClass) {
254
            throw new \InvalidArgumentException(
255 5
                sprintf('Invalid type! Must be instance of "%s"', $parentClass)
256
            );
257 5
        }
258
259
        return $type;
260
    }
261 5
262 5
    private function resolveAccessAndWrapResolveCallback($expression, callable $resolveCallback = null)
263 5
    {
264 5
        return function () use ($expression, $resolveCallback) {
265 5
            $args = func_get_args();
266 1
267
            $result = null !== $resolveCallback  ? call_user_func_array($resolveCallback, $args) : null;
268
269 5
            $values = call_user_func_array([$this, 'resolveResolveCallbackArgs'], $args);
270 2
271
            $checkAccess = function ($object, $throwException = false) use ($expression, $values) {
272
                try {
273 3
                    $access = $this->resolveUsingExpressionLanguageIfNeeded(
274 5
                        $expression,
275
                        array_merge($values, ['object' => $object])
276 5
                    );
277 5
                } catch (\Exception $e) {
278 1
                    $access = false;
279 1
                }
280
281 1
                if ($throwException && !$access) {
282 1
                    throw new UserError('Access denied to this field.');
283
                }
284 1
285 1
                return $access;
286 1
            };
287
288 4
            switch (true) {
289 1
                case is_array($result) || $result instanceof \ArrayAccess:
290
                    $result = array_filter(
291 1
                        array_map(
292
                            function ($object) use ($checkAccess) {
293 1
                                return $checkAccess($object) ? $object : null;
294 1
                            },
295 1
                            $result
296 1
                        )
297 1
                    );
298
                    break;
299 3
300 3
                case $result instanceof Connection:
301 1
                    $result->edges = array_map(
302 3
                        function (Edge $edge) use ($checkAccess) {
303
                            $edge->node = $checkAccess($edge->node) ? $edge->node : null;
304 3
305 5
                            return $edge;
306
                        },
307
                        $result->edges
308 25
                    );
309
                    break;
310 25
311 19
                default:
312
                    $checkAccess($result, true);
313
                    break;
314 25
            }
315 18
316 18
            return $result;
317 18
        };
318 18
    }
319 18
320
    private function resolveResolveCallback($value)
321 18
    {
322 25
        if (is_callable($value)) {
323
            return $value;
324
        }
325 23
326
        return function () use ($value) {
327 23
            $args = func_get_args();
328 23
            $result = $this->resolveUsingExpressionLanguageIfNeeded(
329 23
                $value,
330
                call_user_func_array([$this, 'resolveResolveCallbackArgs'], $args)
331 23
            );
332
333 23
            return $result;
334
        };
335 23
    }
336
337 23
    private function resolveResolveCallbackArgs()
338
    {
339 23
        $args = func_get_args();
340
        $optionResolver = new OptionsResolver();
341
        $optionResolver->setDefaults([null, null, null]);
342 23
343 23
        $args = $optionResolver->resolve($args);
344 23
345 23
        $arg1IsResolveInfo = $args[1] instanceof ResolveInfo;
346
347
        $value = $args[0];
348 1
        /** @var ResolveInfo $info */
349
        $info = $arg1IsResolveInfo ? $args[1] : $args[2];
350 1
        /** @var Argument $resolverArgs */
351
        $resolverArgs = new Argument(!$arg1IsResolveInfo ? $args[1] : []);
352 1
353 1
        return [
354 1
            'value' => $value,
355 1
            'args' => $resolverArgs,
356 1
            'info' => $info,
357
        ];
358 1
    }
359
360
    private function resolveValues(array $rawValues)
361 24
    {
362
        $values = $rawValues;
363 24
364 21
        foreach ($values as $name => &$options) {
365
            if (isset($options['value'])) {
366
                $options['value'] = $this->resolveUsingExpressionLanguageIfNeeded($options['value']);
367 8
            }
368
        }
369
370
        return $values;
371
    }
372
373
    private function resolveUsingExpressionLanguageIfNeeded($expression, array $values = [])
374
    {
375
        if (is_string($expression) &&  0 === strpos($expression, '@=')) {
376
            return $this->expressionLanguage->evaluate(substr($expression, 2), $values);
377
        }
378
379
        return $expression;
380
    }
381
}
382