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

Completed
Push — master ( 62e1fb...805ef9 )
by Jérémiah
45s
created

ConfigResolver::setDefaultResolveFn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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