Failed Conditions
Push — master ( 05fc61...964283 )
by Adrien
02:24
created

methodToConfiguration()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
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->getName()) {
88
            $arg->setName($param->getName());
89
        }
90
91 9
        if (!$arg->getDescription()) {
92 9
            $arg->setDescription($docBlock->getParameterDescription($param));
93
        }
94
95 9
        if (!$arg->hasDefaultValue() && $param->isDefaultValueAvailable()) {
96 6
            $arg->setDefaultValue($param->getDefaultValue());
97
        }
98
99 9
        $this->completeArgumentTypeFromTypeHint($arg, $method, $param, $docBlock);
100
    }
101
102
    /**
103
     * Complete a single argument type from its type hint and doc block.
104
     */
105 9
    private function completeArgumentTypeFromTypeHint(Argument $arg, ReflectionMethod $method, ReflectionParameter $param, DocBlockReader $docBlock): void
106
    {
107 9
        if (!$arg->getTypeInstance()) {
108 8
            $typeDeclaration = $docBlock->getParameterType($param);
109 8
            $this->throwIfArray($param, $typeDeclaration);
110 8
            $arg->setTypeInstance($this->getTypeFromPhpDeclaration($method->getDeclaringClass(), $typeDeclaration, true));
111
        }
112
113 9
        $type = $param->getType();
114 9
        if (!$arg->getTypeInstance() && $type instanceof ReflectionNamedType) {
115 7
            $this->throwIfArray($param, $type->getName());
116 6
            $arg->setTypeInstance($this->reflectionTypeToType($type, true));
117
        }
118
119 8
        $this->nonNullIfHasDefault($arg);
120
121 8
        $this->throwIfNotInputType($param, $arg);
122
    }
123
124
    /**
125
     * Get a GraphQL type instance from dock block return type.
126
     */
127 11
    private function getTypeFromDocBock(ReflectionMethod $method, DocBlockReader $docBlock): ?Type
128
    {
129 11
        $typeDeclaration = $docBlock->getReturnType();
130 11
        $blacklist = [
131
            'Collection',
132
            'array',
133
        ];
134
135 11
        if ($typeDeclaration && !in_array($typeDeclaration, $blacklist, true)) {
136 7
            return $this->getTypeFromPhpDeclaration($method->getDeclaringClass(), $typeDeclaration);
137
        }
138
139 9
        return null;
140
    }
141
142
    /**
143
     * Complete field type from doc blocks and type hints.
144
     */
145 11
    private function completeFieldType(Field $field, ReflectionMethod $method, string $fieldName, DocBlockReader $docBlock): void
146
    {
147 11
        if ($this->isIdentityField($fieldName)) {
148 9
            $field->type = Type::nonNull(Type::id());
149
        }
150
151
        // If still no type, look for docBlock
152 11
        if (!$field->type) {
153 11
            $field->type = $this->getTypeFromDocBock($method, $docBlock);
154
        }
155
156
        // If still no type, look for type hint
157 11
        if (!$field->type) {
158 9
            $field->type = $this->getTypeFromReturnTypeHint($method, $fieldName);
159
        }
160
161
        // If still no type, cannot continue
162 10
        if (!$field->type) {
163 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.');
164
        }
165
    }
166
}
167