Completed
Push — master ( 9e9a0d...740d0f )
by Rafael
04:13
created

ResolverExecutor::__invoke()   C

Complexity

Conditions 11
Paths 78

Size

Total Lines 58
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 11.2983

Importance

Changes 0
Metric Value
cc 11
eloc 36
nc 78
nop 4
dl 0
loc 58
ccs 32
cts 37
cp 0.8649
crap 11.2983
rs 6.4179
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*******************************************************************************
3
 *  This file is part of the GraphQL Bundle package.
4
 *
5
 *  (c) YnloUltratech <[email protected]>
6
 *
7
 *  For the full copyright and license information, please view the LICENSE
8
 *  file that was distributed with this source code.
9
 ******************************************************************************/
10
11
namespace Ynlo\GraphQLBundle\Resolver;
12
13
use Doctrine\Common\Util\ClassUtils;
14
use GraphQL\Type\Definition\ResolveInfo;
15
use GraphQL\Type\Definition\Type;
16
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
17
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
18
use Symfony\Component\DependencyInjection\ContainerInterface;
19
use Symfony\Component\PropertyAccess\PropertyAccessor;
20
use Ynlo\GraphQLBundle\Definition\ExecutableDefinitionInterface;
21
use Ynlo\GraphQLBundle\Definition\FieldDefinition;
22
use Ynlo\GraphQLBundle\Definition\ObjectDefinitionInterface;
23
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
24
use Ynlo\GraphQLBundle\Model\ID;
25
26
/**
27
 * This resolver act as a middleware between the executableDefinition and final resolvers.
28
 * Using injection of parameters can resolve the parameters needed by the final resolver before invoke
29
 */
30
class ResolverExecutor implements ContainerAwareInterface
31
{
32
    use ContainerAwareTrait;
33
34
    /**
35
     * @var ExecutableDefinitionInterface
36
     */
37
    protected $executableDefinition;
38
39
    /**
40
     * @var Endpoint
41
     */
42
    protected $endpoint;
43
44
    /**
45
     * @var mixed
46
     */
47
    protected $root;
48
49
    /**
50
     * @var ResolveInfo
51
     */
52
    protected $resolveInfo;
53
54
    /**
55
     * @var mixed
56
     */
57
    protected $context;
58
59
    /**
60
     * @var array
61
     */
62
    protected $args = [];
63
64
    /**
65
     * @param ContainerInterface            $container
66
     * @param Endpoint                      $endpoint
67
     * @param ExecutableDefinitionInterface $executableDefinition
68
     */
69 18
    public function __construct(ContainerInterface $container, Endpoint $endpoint, ExecutableDefinitionInterface $executableDefinition)
70
    {
71 18
        $this->executableDefinition = $executableDefinition;
72 18
        $this->endpoint = $endpoint;
73 18
        $this->container = $container;
74 18
    }
75
76
    /**
77
     * @param mixed       $root
78
     * @param array       $args
79
     * @param mixed       $context
80
     * @param ResolveInfo $resolveInfo
81
     *
82
     * @return mixed
83
     *
84
     * @throws \Exception
85
     */
86 20
    public function __invoke($root, array $args, $context, ResolveInfo $resolveInfo)
87
    {
88 20
        $this->root = $root;
89 20
        $this->args = $args;
90 20
        $this->context = $context;
91 20
        $this->resolveInfo = $resolveInfo;
92
93 20
        $resolverName = $this->executableDefinition->getResolver();
94
95 20
        $resolver = null;
96 20
        $refMethod = null;
97
98 20
        if (class_exists($resolverName)) {
99 20
            $refClass = new \ReflectionClass($resolverName);
100
101
            /** @var callable $resolver */
102 20
            $resolver = $refClass->newInstance();
103 20
            if ($resolver instanceof ContainerAwareInterface) {
104 20
                $resolver->setContainer($this->container);
105
            }
106 20
            if ($refClass->hasMethod('__invoke')) {
107 20
                $refMethod = $refClass->getMethod('__invoke');
108
            }
109
        } elseif (method_exists($root, $resolverName)) {
110
            $resolver = $root;
111
            $refMethod = new \ReflectionMethod(ClassUtils::getClass($root), $resolverName);
112
        }
113
114 20
        if ($resolver && $refMethod) {
115 20
            $resolveContext = new ResolverContext();
116 20
            $resolveContext->setDefinition($this->executableDefinition);
117 20
            $resolveContext->setArgs($args);
118 20
            $resolveContext->setRoot($root);
119 20
            $resolveContext->setEndpoint($this->endpoint);
120 20
            $resolveContext->setResolveInfo($resolveInfo);
121
122 20
            $type = $this->executableDefinition->getType();
123 20
            if ($this->executableDefinition->hasMeta('node')) {
124 10
                $type = $this->executableDefinition->getMeta('node');
125
            }
126
127 20
            if ($this->endpoint->hasType($type)) {
128 20
                if ($nodeDefinition = $this->endpoint->getType($type)) {
129 20
                    $resolveContext->setNodeDefinition($nodeDefinition);
130
                }
131
            }
132
133 20
            if ($resolver instanceof AbstractResolver) {
134 20
                $resolver->setContext($resolveContext);
135
            }
136
137 20
            $params = $this->prepareMethodParameters($refMethod, $args);
138
139 20
            return $refMethod->invokeArgs($resolver, $params);
0 ignored issues
show
Bug introduced by
It seems like $resolver can also be of type callable; however, parameter $object of ReflectionMethod::invokeArgs() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

139
            return $refMethod->invokeArgs(/** @scrutinizer ignore-type */ $resolver, $params);
Loading history...
140
        }
141
142
        $error = sprintf('The resolver "%s" for executableDefinition "%s" is not a valid resolver. Resolvers should have a method "__invoke(...)"', $resolverName, $this->executableDefinition->getName());
143
        throw new \RuntimeException($error);
144
    }
145
146
    /**
147
     * @param \ReflectionMethod $refMethod
148
     * @param array             $args
149
     *
150
     * @throws \Exception
151
     *
152
     * @return array
153
     */
154 20
    protected function prepareMethodParameters(\ReflectionMethod $refMethod, array $args): array
155
    {
156
        //normalize arguments
157 20
        $normalizedArguments = [];
158 20
        foreach ($args as $key => $value) {
159 20
            if ($this->executableDefinition->hasArgument($key)) {
160 20
                $argument = $this->executableDefinition->getArgument($key);
161 20
                if ('input' === $key) {
162 7
                    $normalizedValue = $value;
163
                } else {
164 13
                    $normalizedValue = $this->normalizeValue($value, $argument->getType());
165
166
                    //normalize argument into respective inputs objects
167 13
                    if (is_array($normalizedValue) && $this->endpoint->hasType($argument->getType())) {
168 7
                        if ($argument->isList()) {
169 7
                            $tmp = [];
170 7
                            foreach ($normalizedValue as $childValue) {
171 7
                                $tmp[] = $this->arrayToObject($childValue, $this->endpoint->getType($argument->getType()));
172
                            }
173 7
                            $normalizedValue = $tmp;
174
                        } else {
175
                            $normalizedValue = $this->arrayToObject($normalizedValue, $this->endpoint->getType($argument->getType()));
176
                        }
177
                    }
178
                }
179 20
                $normalizedArguments[$argument->getName()] = $normalizedValue;
180 20
                $normalizedArguments[$argument->getInternalName()] = $normalizedValue;
181
            }
182
        }
183
184 20
        $normalizedArguments['args'] = $normalizedArguments;
185 20
        $normalizedArguments['root'] = $this->root;
186 20
        $indexedArguments = $this->resolveMethodArguments($refMethod, $normalizedArguments);
187 20
        ksort($indexedArguments);
188
189 20
        return $indexedArguments;
190
    }
191
192
    /**
193
     * @param \ReflectionMethod $method
194
     * @param array             $incomeArgs
195
     *
196
     * @return array
197
     */
198 20
    protected function resolveMethodArguments(\ReflectionMethod $method, array $incomeArgs)
199
    {
200 20
        $orderedArguments = [];
201 20
        foreach ($method->getParameters() as $parameter) {
202 20
            if ($parameter->isOptional()) {
203 10
                $orderedArguments[$parameter->getPosition()] = $parameter->getDefaultValue();
204
            }
205 20
            foreach ($incomeArgs as $key => $value) {
206 20
                if ($parameter->getName() === $key) {
207 20
                    $orderedArguments[$parameter->getPosition()] = $value;
208 20
                    continue 2;
209
                }
210
            }
211
212
            //inject root common argument
213
            if ($this->root
214
                && 'root' === $parameter->getName()
215
                && $parameter->getClass()
216
                && $parameter->getClass()->isInstance($this->root)
217
218
            ) {
219
                $orderedArguments[$parameter->getPosition()] = $this->root;
220
            }
221
        }
222
223 20
        return $orderedArguments;
224
    }
225
226
    /**
227
     * @param mixed  $value
228
     * @param string $type
229
     *
230
     * @return mixed
231
     */
232 13
    protected function normalizeValue($value, string $type)
233
    {
234 13
        if (Type::ID === $type) {
235 4
            if (\is_array($value)) {
236 2
                $idsArray = [];
237 2
                foreach ($value as $id) {
238 2
                    $idsArray[] = ID::createFromString($id);
239
                }
240 2
                $value = $idsArray;
241
            } else {
242 2
                $value = ID::createFromString($value);
243
            }
244
        }
245
246 13
        return $value;
247
    }
248
249
    /**
250
     * Convert a array into object using given definition
251
     *
252
     * @param array                     $data       data to populate the object
253
     * @param ObjectDefinitionInterface $definition object definition
254
     *
255
     * @return mixed
256
     */
257 7
    protected function arrayToObject(array $data, ObjectDefinitionInterface $definition)
258
    {
259 7
        $class = $definition->getClass();
260
261
        //normalize data
262 7
        foreach ($data as $fieldName => &$value) {
263 7
            if (!$definition->hasField($fieldName)) {
264
                continue;
265
            }
266 7
            $fieldDefinition = $definition->getField($fieldName);
267 7
            $value = $this->normalizeValue($value, $fieldDefinition->getType());
268
        }
269 7
        unset($value);
270
271
        //instantiate object
272 7
        if (class_exists($class)) {
273 7
            $object = new $class();
274
        } else {
275
            return $data;
276
        }
277
278
        //populate object
279 7
        foreach ($data as $key => $value) {
280 7
            if (!$definition->hasField($key)) {
281
                continue;
282
            }
283 7
            $fieldDefinition = $definition->getField($key);
284 7
            $this->setObjectValue($object, $fieldDefinition, $value);
285
        }
286
287 7
        return $object;
288
    }
289
290
    /**
291
     * @param mixed           $object
292
     * @param FieldDefinition $fieldDefinition
293
     * @param mixed           $value
294
     */
295 7
    protected function setObjectValue($object, FieldDefinition $fieldDefinition, $value)
296
    {
297
        //using setter
298 7
        $accessor = new PropertyAccessor();
299 7
        $propertyName = $fieldDefinition->getOriginName();
300 7
        if ($propertyName) {
301 7
            if ($accessor->isWritable($object, $propertyName)) {
302 7
                $accessor->setValue($object, $propertyName, $value);
303
            } else {
304
                //using reflection
305
                $refClass = new \ReflectionClass(\get_class($object));
306
                if ($refClass->hasProperty($fieldDefinition->getOriginName()) && $property = $refClass->getProperty($fieldDefinition->getOriginName())) {
307
                    $property->setAccessible(true);
308
                    $property->setValue($object, $value);
309
                }
310
            }
311
        }
312 7
    }
313
}
314