Completed
Pull Request — master (#7)
by Yonel Ceruto
10:45 queued 04:14
created

ObjectFieldResolver::__invoke()   C

Complexity

Conditions 13
Paths 48

Size

Total Lines 60
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 13.0267

Importance

Changes 0
Metric Value
dl 0
loc 60
ccs 35
cts 37
cp 0.9459
rs 6.3453
c 0
b 0
f 0
cc 13
eloc 37
nc 48
nop 4
crap 13.0267

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\Collections\Collection;
14
use Doctrine\Common\Persistence\Proxy;
15
use GraphQL\Deferred;
16
use GraphQL\Error\Error;
17
use GraphQL\Type\Definition\ResolveInfo;
18
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
19
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
20
use Symfony\Component\DependencyInjection\ContainerInterface;
21
use Symfony\Component\PropertyAccess\PropertyAccessor;
22
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
23
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
24
use Ynlo\GraphQLBundle\Definition\FieldDefinition;
25
use Ynlo\GraphQLBundle\Definition\FieldsAwareDefinitionInterface;
26
use Ynlo\GraphQLBundle\Definition\QueryDefinition;
27
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
28
use Ynlo\GraphQLBundle\Model\ID;
29
use Ynlo\GraphQLBundle\Model\NodeInterface;
30
use Ynlo\GraphQLBundle\Type\Definition\EndpointAwareInterface;
31
use Ynlo\GraphQLBundle\Type\Definition\EndpointAwareTrait;
32
use Ynlo\GraphQLBundle\Type\Types;
33
34
/**
35
 * Default resolver for all object fields
36
 */
37
class ObjectFieldResolver implements ContainerAwareInterface, EndpointAwareInterface
38
{
39
    use ContainerAwareTrait;
40
    use EndpointAwareTrait;
41
42
    /**
43
     * @var int[]
44
     */
45
    private static $concurrentUsages = [];
46
47
    protected $definition;
48
    protected $deferredBuffer;
49
    protected $authorizationChecker;
50
51 22
    public function __construct(ContainerInterface $container, Endpoint $endpoint, FieldsAwareDefinitionInterface $definition, DeferredBuffer $deferredBuffer, AuthorizationCheckerInterface $authorizationChecker)
52
    {
53 22
        $this->container = $container;
54 22
        $this->endpoint = $endpoint;
55 22
        $this->definition = $definition;
56 22
        $this->deferredBuffer = $deferredBuffer;
57 22
        $this->authorizationChecker = $authorizationChecker;
58 22
    }
59
60
    /**
61
     * @param mixed       $root
62
     * @param array       $args
63
     * @param mixed       $context
64
     * @param ResolveInfo $info
65
     *
66
     * @return mixed|null|string
67
     *
68
     * @throws \Exception
69
     */
70 22
    public function __invoke($root, array $args, $context, ResolveInfo $info)
71
    {
72 22
        $value = null;
73 22
        $fieldDefinition = $this->definition->getField($info->fieldName);
74 22
        $this->verifyConcurrentUsage($fieldDefinition);
75 22
        $this->denyAccessUnlessGranted($fieldDefinition);
76
77
        //when use external resolver or use a object method with arguments
78 22
        if (($resolver = $fieldDefinition->getResolver()) || $fieldDefinition->getArguments()) {
79 19
            $queryDefinition = new QueryDefinition();
80 19
            $queryDefinition->setName($fieldDefinition->getName());
81 19
            $queryDefinition->setType($fieldDefinition->getType());
82 19
            $queryDefinition->setNode($fieldDefinition->getNode());
83 19
            $queryDefinition->setArguments($fieldDefinition->getArguments());
84 19
            $queryDefinition->setList($fieldDefinition->isList());
85 19
            $queryDefinition->setRoles($fieldDefinition->getRoles());
86 19
            $queryDefinition->setMetas($fieldDefinition->getMetas());
87
88 19
            if ($resolver) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $resolver of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
89 19
                $queryDefinition->setResolver($resolver);
90
            } elseif ($fieldDefinition->getOriginType() === \ReflectionMethod::class) {
91
                $queryDefinition->setResolver($fieldDefinition->getOriginName());
92
            }
93
94 19
            $resolver = new ResolverExecutor($this->container, $this->endpoint, $queryDefinition);
95 19
            $value = $resolver($root, $args, $context, $info);
96
        } else {
97 22
            $accessor = new PropertyAccessor(true);
98 22
            $originName = $fieldDefinition->getOriginName() ?: $fieldDefinition->getName();
99 22
            $value = $accessor->getValue($root, $originName);
100
        }
101
102 22
        if (null !== $value && Types::ID === $fieldDefinition->getType()) {
103
            //ID are formed with base64 representation of the Types and real database ID
104
            //in order to create a unique and global identifier for each resource
105
            //@see https://facebook.github.io/relay/docs/graphql-object-identification.html
106 17
            if ($value instanceof ID) {
107 2
                $value = (string) $value;
108
            } else {
109 16
                $value = (string) new ID($this->definition->getName(), $value);
110
            }
111
        }
112
113 22
        if ($value instanceof Collection) {
114 3
            $value = $value->toArray();
115
        }
116
117 22
        if ($value instanceof Proxy && $value instanceof NodeInterface && !$value->__isInitialized()) {
118 4
            $this->deferredBuffer->add($value);
119
120 4
            return new Deferred(
121 4
                function () use ($value) {
122 4
                    $this->deferredBuffer->loadBuffer();
123
124 4
                    return $this->deferredBuffer->getLoadedEntity($value);
125 4
                }
126
            );
127
        }
128
129 22
        return $value;
130
    }
131
132
    /**
133
     * @param FieldDefinition $definition
134
     *
135
     * @throws Error
136
     */
137 22
    private function verifyConcurrentUsage(FieldDefinition $definition)
138
    {
139 22
        if ($maxConcurrentUsage = $definition->getMaxConcurrentUsage()) {
140 2
            $oid = spl_object_hash($definition);
141 2
            $usages = static::$concurrentUsages[$oid] ?? 1;
0 ignored issues
show
Bug introduced by
Since $concurrentUsages is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $concurrentUsages to at least protected.
Loading history...
142 2
            if ($usages > $maxConcurrentUsage) {
143 1
                if (1 === $maxConcurrentUsage) {
144 1
                    $error = sprintf(
145 1
                        'The field "%s" can be fetched only once per query. This field can`t be used in a list.',
146 1
                        $definition->getName()
147
                    );
148
                } else {
149
                    $error = sprintf(
150
                        'The field "%s" can`t be fetched more than %s times per query.',
151
                        $definition->getName(),
152
                        $maxConcurrentUsage
153
                    );
154
                }
155 1
                throw new Error($error);
156
            }
157 1
            static::$concurrentUsages[$oid] = $usages + 1;
158
        }
159 22
    }
160
161
    /**
162
     * @throws Error
163
     */
164 22
    private function denyAccessUnlessGranted(FieldDefinition $fieldDefinition): void
165
    {
166 22
        if ($fieldDefinition->hasMeta('roles')) {
167
            $roles = $fieldDefinition->getMeta('roles');
168
        } else {
169 22
            $roles = $fieldDefinition->getRoles();
170
        }
171
172 22
        if ($roles && !$this->authorizationChecker->isGranted($roles, $fieldDefinition)) {
173
            throw new Error(sprintf('Access denied to "%s" field', $fieldDefinition->getName()));
174
        }
175 22
    }
176
}
177