Completed
Pull Request — master (#122)
by Sebastian
03:27
created

QueryType::resolvePossibleTypes()   D

Complexity

Conditions 9
Paths 14

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 36.2741

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 33
ccs 7
cts 23
cp 0.3043
rs 4.909
cc 9
eloc 17
nc 14
nop 3
crap 36.2741
1
<?php
2
/**
3
 * Date: 03.12.15
4
 *
5
 * @author Portey Vasil <[email protected]>
6
 */
7
8
namespace Youshido\GraphQL\Introspection;
9
10
use Youshido\GraphQL\Execution\ResolveInfo;
11
use Youshido\GraphQL\Field\Field;
12
use Youshido\GraphQL\Introspection\Traits\TypeCollectorTrait;
13
use Youshido\GraphQL\Type\AbstractType;
14
use Youshido\GraphQL\Type\CompositeTypeInterface;
15
use Youshido\GraphQL\Type\Enum\AbstractEnumType;
16
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
17
use Youshido\GraphQL\Type\ListType\ListType;
18
use Youshido\GraphQL\Type\NonNullType;
19
use Youshido\GraphQL\Type\Object\AbstractObjectType;
20
use Youshido\GraphQL\Type\Scalar\BooleanType;
21
use Youshido\GraphQL\Type\TypeMap;
22
use Youshido\GraphQL\Type\Union\AbstractUnionType;
23
24
class QueryType extends AbstractObjectType
25
{
26
27
    use TypeCollectorTrait;
28
29
    /**
30
     * @return String type name
31
     */
32 63
    public function getName()
33
    {
34 63
        return '__Type';
35
    }
36
37 3
    public function resolveOfType(AbstractType $value)
38
    {
39 3
        if ($value instanceof CompositeTypeInterface) {
40 2
            return $value->getTypeOf();
41
        }
42
43 3
        return null;
44
    }
45
46 3
    public function resolveInputFields($value)
47
    {
48 3
        if ($value instanceof AbstractInputObjectType) {
49
            /** @var AbstractObjectType $value */
50
            return $value->getConfig()->getFields();
0 ignored issues
show
Documentation Bug introduced by
The method getFields does not exist on object<Youshido\GraphQL\Config\AbstractConfig>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
51
        }
52
53 3
        return null;
54
    }
55
56 2
    public function resolveEnumValues($value, $args)
57
    {
58
        /** @var $value AbstractType|AbstractEnumType */
59 2
        if ($value && $value->getKind() == TypeMap::KIND_ENUM) {
60 2
            $data = [];
61 2
            foreach ($value->getValues() as $enumValue) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Youshido\GraphQL\Type\AbstractType as the method getValues() does only exist in the following sub-classes of Youshido\GraphQL\Type\AbstractType: Examples\Blog\Schema\PostStatus, Youshido\GraphQL\Type\Enum\AbstractEnumType, Youshido\GraphQL\Type\Enum\EnumType, Youshido\Tests\DataProvider\TestEnumType, Youshido\Tests\StarWars\Schema\EpisodeEnum. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
62 2
                if(!$args['includeDeprecated'] && (isset($enumValue['isDeprecated']) && $enumValue['isDeprecated'])) {
63
                    continue;
64
                }
65
66 2
                if (!array_key_exists('description', $enumValue)) {
67 2
                    $enumValue['description'] = '';
68 2
                }
69 2
                if (!array_key_exists('isDeprecated', $enumValue)) {
70 2
                    $enumValue['isDeprecated'] = false;
71 2
                }
72 2
                if (!array_key_exists('deprecationReason', $enumValue)) {
73 2
                    $enumValue['deprecationReason'] = '';
74 2
                }
75
76 2
                $data[] = $enumValue;
77 2
            }
78
79 2
            return $data;
80
        }
81
82 2
        return null;
83
    }
84
85 4
    public function resolveFields($value, $args)
86
    {
87
        /** @var AbstractType $value */
88 4
        if (!$value ||
89 4
            in_array($value->getKind(), [TypeMap::KIND_SCALAR, TypeMap::KIND_UNION, TypeMap::KIND_INPUT_OBJECT, TypeMap::KIND_ENUM])
90 4
        ) {
91 3
            return null;
92
        }
93
94
        /** @var AbstractObjectType $value */
95 4
        return array_filter($value->getConfig()->getFields(), function ($field) use ($args) {
0 ignored issues
show
Documentation Bug introduced by
The method getFields does not exist on object<Youshido\GraphQL\Config\AbstractConfig>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
96
            /** @var $field Field */
97 4
            if (in_array($field->getName(), ['__type', '__schema']) || (!$args['includeDeprecated'] && $field->isDeprecated())) {
98 4
                return false;
99
            }
100
101 4
            return true;
102 4
        });
103
    }
104
105 3
    public function resolveInterfaces($value)
106
    {
107
        /** @var $value AbstractType */
108 3
        if ($value->getKind() == TypeMap::KIND_OBJECT) {
109
            /** @var $value AbstractObjectType */
110 3
            return $value->getConfig()->getInterfaces() ?: [];
0 ignored issues
show
Documentation Bug introduced by
The method getInterfaces does not exist on object<Youshido\GraphQL\Config\AbstractConfig>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
111
        }
112
113 2
        return null;
114
    }
115
116 3
    public function resolvePossibleTypes($value, $args, ResolveInfo $info)
117
    {
118
        /** @var $value AbstractObjectType */
119 3
        if ($value->getKind() == TypeMap::KIND_INTERFACE) {
120 2
            $this->collectTypes($info->getExecutionContext()->getSchema()->getQueryType());
121 2
            foreach ($schema->getTypesList()->getTypes() as $type) {
0 ignored issues
show
Bug introduced by
The variable $schema does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
122
              $this->collectTypes($type);
123
            }
124
125
            $possibleTypes = [];
126
            foreach ($this->types as $type) {
127
                /** @var $type AbstractObjectType */
128
                if ($type->getKind() == TypeMap::KIND_OBJECT) {
129
                    $interfaces = $type->getConfig()->getInterfaces();
0 ignored issues
show
Documentation Bug introduced by
The method getInterfaces does not exist on object<Youshido\GraphQL\Config\AbstractConfig>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
130
131
                    if ($interfaces) {
132
                        foreach ($interfaces as $interface) {
133
                            if ($interface->getName() == $value->getName()) {
134
                                $possibleTypes[] = $type;
135
                            }
136
                        }
137
                    }
138
                }
139
            }
140
141
            return $possibleTypes;
142 3
        } elseif ($value->getKind() == TypeMap::KIND_UNION) {
143
            /** @var $value AbstractUnionType */
144 1
            return $value->getTypes();
145
        }
146
147 3
        return null;
148
    }
149
150 8
    public function build($config)
151
    {
152
        $config
153 8
            ->addField('name', TypeMap::TYPE_STRING)
154 8
            ->addField('kind', new NonNullType(TypeMap::TYPE_STRING))
155 8
            ->addField('description', TypeMap::TYPE_STRING)
156 8
            ->addField('ofType', [
157 8
                'type'    => new QueryType(),
158 8
                'resolve' => [$this, 'resolveOfType']
159 8
            ])
160 8
            ->addField(new Field([
161 8
                'name'    => 'inputFields',
162 8
                'type'    => new ListType(new NonNullType(new InputValueType())),
163 8
                'resolve' => [$this, 'resolveInputFields']
164 8
            ]))
165 8
            ->addField(new Field([
166 8
                'name'    => 'enumValues',
167
                'args'    => [
168
                    'includeDeprecated' => [
169 8
                        'type'    => new BooleanType(),
170
                        'defaultValue' => false
171 8
                    ]
172 8
                ],
173 8
                'type'    => new ListType(new NonNullType(new EnumValueType())),
174 8
                'resolve' => [$this, 'resolveEnumValues']
175 8
            ]))
176 8
            ->addField(new Field([
177 8
                'name'    => 'fields',
178
                'args'    => [
179
                    'includeDeprecated' => [
180 8
                        'type'    => new BooleanType(),
181
                        'defaultValue' => false
182 8
                    ]
183 8
                ],
184 8
                'type'    => new ListType(new NonNullType(new FieldType())),
185 8
                'resolve' => [$this, 'resolveFields']
186 8
            ]))
187 8
            ->addField(new Field([
188 8
                'name'    => 'interfaces',
189 8
                'type'    => new ListType(new NonNullType(new QueryType())),
190 8
                'resolve' => [$this, 'resolveInterfaces']
191 8
            ]))
192 8
            ->addField('possibleTypes', [
193 8
                'type'    => new ListType(new NonNullType(new QueryType())),
194 8
                'resolve' => [$this, 'resolvePossibleTypes']
195 8
            ]);
196 8
    }
197
198
}
199