Passed
Push — master ( 63906e...ca13db )
by Adrien
16:07 queued 13:12
created

OutputFieldsConfigurationFactory   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 141
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 24
eloc 53
c 5
b 0
f 0
dl 0
loc 141
ccs 58
cts 58
cp 1
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getMethodPattern() 0 3 1
A methodToConfiguration() 0 11 2
A completeField() 0 14 3
A completeFieldArguments() 0 16 2
A getTypeFromDocBock() 0 13 3
A completeFieldType() 0 19 5
A completeArgumentTypeFromTypeHint() 0 17 4
A completeArgumentFromTypeHint() 0 11 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Doctrine\Factory;
6
7
use GraphQL\Doctrine\Attribute\Argument;
8
use GraphQL\Doctrine\Attribute\Field;
9
use GraphQL\Doctrine\DocBlockReader;
10
use GraphQL\Doctrine\Exception;
11
use GraphQL\Type\Definition\Type;
12
use ReflectionMethod;
13
use ReflectionNamedType;
14
use ReflectionParameter;
15
16
/**
17
 * A factory to create a configuration for all getters of an entity.
18
 */
19
final class OutputFieldsConfigurationFactory extends AbstractFieldsConfigurationFactory
20
{
21 14
    protected function getMethodPattern(): string
22
    {
23 14
        return '~^(get|is|has)[A-Z]~';
24
    }
25
26
    /**
27
     * Get the entire configuration for a method.
28
     */
29 14
    protected function methodToConfiguration(ReflectionMethod $method): ?array
30
    {
31
        // Get a field from attribute, or an empty one
32 14
        $field = $this->reader->getAttribute($method, Field::class) ?? new Field();
33
34 14
        if (!$field->type instanceof Type) {
35 14
            $field->type = $this->getTypeFromPhpDeclaration($method->getDeclaringClass(), $field->type);
36 14
            $this->completeField($field, $method);
37
        }
38
39 9
        return $field->toArray();
0 ignored issues
show
Bug introduced by
The method toArray() does not exist on GraphQL\Doctrine\Attribute\ApiAttribute. It seems like you code against a sub-type of GraphQL\Doctrine\Attribute\ApiAttribute such as GraphQL\Doctrine\Attribute\Field or GraphQL\Doctrine\Attribute\AbstractAttribute. ( Ignorable by Annotation )

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

39
        return $field->/** @scrutinizer ignore-call */ toArray();
Loading history...
40
    }
41
42
    /**
43
     * Complete field with info from doc blocks and type hints.
44
     */
45 14
    private function completeField(Field $field, ReflectionMethod $method): void
46
    {
47 14
        $fieldName = lcfirst(preg_replace('~^get~', '', $method->getName()) ?? '');
48 14
        if (!$field->name) {
49 14
            $field->name = $fieldName;
50
        }
51
52 14
        $docBlock = new DocBlockReader($method);
53 14
        if (!$field->description) {
54 14
            $field->description = $docBlock->getMethodDescription();
55
        }
56
57 14
        $this->completeFieldArguments($field, $method, $docBlock);
58 11
        $this->completeFieldType($field, $method, $fieldName, $docBlock);
59
    }
60
61
    /**
62
     * Complete arguments configuration from existing type hints.
63
     */
64 14
    private function completeFieldArguments(Field $field, ReflectionMethod $method, DocBlockReader $docBlock): void
65
    {
66 14
        $args = [];
67 14
        foreach ($method->getParameters() as $param) {
68
            // Either get existing, or create new argument
69 9
            $arg = $this->reader->getAttribute($param, Argument::class) ?? new Argument();
70 9
            $arg->setName($param->getName());
0 ignored issues
show
Bug introduced by
The method setName() does not exist on GraphQL\Doctrine\Attribute\ApiAttribute. It seems like you code against a sub-type of GraphQL\Doctrine\Attribute\ApiAttribute such as GraphQL\Doctrine\Attribute\AbstractAttribute. ( Ignorable by Annotation )

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

70
            $arg->/** @scrutinizer ignore-call */ 
71
                  setName($param->getName());
Loading history...
71
72 9
            $arg->setTypeInstance($this->getTypeFromPhpDeclaration($method->getDeclaringClass(), $arg->getType()));
0 ignored issues
show
Bug introduced by
The method getType() does not exist on GraphQL\Doctrine\Attribute\ApiAttribute. It seems like you code against a sub-type of GraphQL\Doctrine\Attribute\ApiAttribute such as GraphQL\Doctrine\Attribute\AbstractAttribute. ( Ignorable by Annotation )

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

72
            $arg->setTypeInstance($this->getTypeFromPhpDeclaration($method->getDeclaringClass(), $arg->/** @scrutinizer ignore-call */ getType()));
Loading history...
Bug introduced by
The method setTypeInstance() does not exist on GraphQL\Doctrine\Attribute\ApiAttribute. It seems like you code against a sub-type of GraphQL\Doctrine\Attribute\ApiAttribute such as GraphQL\Doctrine\Attribute\AbstractAttribute. ( Ignorable by Annotation )

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

72
            $arg->/** @scrutinizer ignore-call */ 
73
                  setTypeInstance($this->getTypeFromPhpDeclaration($method->getDeclaringClass(), $arg->getType()));
Loading history...
73
74 9
            $args[$param->getName()] = $arg;
75
76 9
            $this->completeArgumentFromTypeHint($arg, $method, $param, $docBlock);
77
        }
78
79 11
        $field->args = $args;
80
    }
81
82
    /**
83
     * Complete a single argument from its type hint.
84
     */
85 9
    private function completeArgumentFromTypeHint(Argument $arg, ReflectionMethod $method, ReflectionParameter $param, DocBlockReader $docBlock): void
86
    {
87 9
        if (!$arg->getDescription()) {
88 9
            $arg->setDescription($docBlock->getParameterDescription($param));
89
        }
90
91 9
        if (!$arg->hasDefaultValue() && $param->isDefaultValueAvailable()) {
92 6
            $arg->setDefaultValue($param->getDefaultValue());
93
        }
94
95 9
        $this->completeArgumentTypeFromTypeHint($arg, $method, $param, $docBlock);
96
    }
97
98
    /**
99
     * Complete a single argument type from its type hint and doc block.
100
     */
101 9
    private function completeArgumentTypeFromTypeHint(Argument $arg, ReflectionMethod $method, ReflectionParameter $param, DocBlockReader $docBlock): void
102
    {
103 9
        if (!$arg->getTypeInstance()) {
104 8
            $typeDeclaration = $docBlock->getParameterType($param);
105 8
            $this->throwIfArray($param, $typeDeclaration);
106 8
            $arg->setTypeInstance($this->getTypeFromPhpDeclaration($method->getDeclaringClass(), $typeDeclaration, true));
107
        }
108
109 9
        $type = $param->getType();
110 9
        if (!$arg->getTypeInstance() && $type instanceof ReflectionNamedType) {
111 7
            $this->throwIfArray($param, $type->getName());
112 6
            $arg->setTypeInstance($this->reflectionTypeToType($type, true));
113
        }
114
115 8
        $this->nonNullIfHasDefault($arg);
116
117 8
        $this->throwIfNotInputType($param, $arg);
118
    }
119
120
    /**
121
     * Get a GraphQL type instance from dock block return type.
122
     */
123 11
    private function getTypeFromDocBock(ReflectionMethod $method, DocBlockReader $docBlock): ?Type
124
    {
125 11
        $typeDeclaration = $docBlock->getReturnType();
126 11
        $blacklist = [
127
            'Collection',
128
            'array',
129
        ];
130
131 11
        if ($typeDeclaration && !in_array($typeDeclaration, $blacklist, true)) {
132 7
            return $this->getTypeFromPhpDeclaration($method->getDeclaringClass(), $typeDeclaration);
133
        }
134
135 9
        return null;
136
    }
137
138
    /**
139
     * Complete field type from doc blocks and type hints.
140
     */
141 11
    private function completeFieldType(Field $field, ReflectionMethod $method, string $fieldName, DocBlockReader $docBlock): void
142
    {
143 11
        if ($this->isIdentityField($fieldName)) {
144 9
            $field->type = Type::nonNull(Type::id());
145
        }
146
147
        // If still no type, look for docBlock
148 11
        if (!$field->type) {
149 11
            $field->type = $this->getTypeFromDocBock($method, $docBlock);
150
        }
151
152
        // If still no type, look for type hint
153 11
        if (!$field->type) {
154 9
            $field->type = $this->getTypeFromReturnTypeHint($method, $fieldName);
155
        }
156
157
        // If still no type, cannot continue
158 10
        if (!$field->type) {
159 1
            throw new Exception('Could not find type for method ' . $this->getMethodFullName($method) . '. Either type hint the return value, or specify the type with `#[API\Field]` attribute.');
160
        }
161
    }
162
}
163