Passed
Push — master ( cecd1e...508b16 )
by Alexander
08:42 queued 06:27
created

ClassConfigFactory::convertTypeToString()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 12
ccs 5
cts 6
cp 0.8333
crap 3.0416
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Proxy;
6
7
use InvalidArgumentException;
8
use Reflection;
9
use ReflectionClass;
10
use ReflectionException;
11
use ReflectionIntersectionType;
0 ignored issues
show
Bug introduced by
The type ReflectionIntersectionType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use ReflectionMethod;
13
use ReflectionNamedType;
14
use ReflectionParameter;
15
use ReflectionUnionType;
16
use Yiisoft\Proxy\Config\ClassConfig;
17
use Yiisoft\Proxy\Config\MethodConfig;
18
use Yiisoft\Proxy\Config\ParameterConfig;
19
use Yiisoft\Proxy\Config\TypeConfig;
20
21
/**
22
 * @internal
23
 *
24
 * A factory for creating class configs ({@see ClassConfig}). Uses PHP `Reflection` to get the necessary metadata.
25
 *
26
 * @link https://www.php.net/manual/en/book.reflection.php
27
 */
28
final class ClassConfigFactory
29
{
30
    /**
31
     * Gets single class config based for individual class.
32
     *
33
     * @param string $className Full class or interface name (including namespace).
34
     *
35
     * @throws InvalidArgumentException In case class or interface does not exist.
36
     *
37
     * @return ClassConfig Class config with all related configs (methods, parameters, types) linked.
38
     */
39 14
    public function getClassConfig(string $className): ClassConfig
40
    {
41
        try {
42 14
            $reflection = new ReflectionClass($className);
43 1
        } catch (ReflectionException) {
44 1
            throw new InvalidArgumentException("$className must exist.");
45
        }
46
47 13
        return new ClassConfig(
48 13
            isInterface: $reflection->isInterface(),
49 13
            namespace: $reflection->getNamespaceName(),
50 13
            modifiers: Reflection::getModifierNames($reflection->getModifiers()),
51 13
            name: $reflection->getName(),
52 13
            shortName: $reflection->getShortName(),
53 13
            parent: (string) $reflection->getParentClass(),
54 13
            interfaces: $reflection->getInterfaceNames(),
55 13
            methods: $this->getMethodConfigs($reflection),
56
        );
57
    }
58
59
    /**
60
     * Gets the complete set of method configs for a given class reflection.
61
     *
62
     * @param ReflectionClass $class Reflection of a class.
63
     *
64
     * @return MethodConfig[] List of method configs. The order is maintained.
65
     */
66 13
    private function getMethodConfigs(ReflectionClass $class): array
67
    {
68 13
        $methods = [];
69 13
        foreach ($class->getMethods() as $method) {
70 12
            $methods[$method->getName()] = $this->getMethodConfig($class, $method);
71
        }
72
73 13
        return $methods;
74
    }
75
76
    /**
77
     * Gets single method config for individual class / method reflection pair.
78
     *
79
     * @param ReflectionClass $class Reflection of a class.
80
     * @param ReflectionMethod $method Reflection of a method.
81
     *
82
     * @return MethodConfig Single method config.
83
     */
84 12
    private function getMethodConfig(ReflectionClass $class, ReflectionMethod $method): MethodConfig
85
    {
86 12
        return new MethodConfig(
87 12
            modifiers: $this->getMethodModifiers($class, $method),
88 12
            name: $method->getName(),
89 12
            parameters: $this->getMethodParameterConfigs($method),
90 12
            returnType: $this->getMethodReturnTypeConfig($method),
91
        );
92
    }
93
94
    /**
95
     * Gets the set of method modifiers for a given class / method reflection pair
96
     *
97
     * @param ReflectionClass $class Reflection of a class.
98
     * @param ReflectionMethod $method Reflection of a method.
99
     *
100
     * @return string[] List of method modifiers.
101
     */
102 12
    private function getMethodModifiers(ReflectionClass $class, ReflectionMethod $method): array
103
    {
104 12
        $modifiers = Reflection::getModifierNames($method->getModifiers());
105 12
        if (!$class->isInterface()) {
106 7
            return $modifiers;
107
        }
108
109 5
        return array_values(array_filter($modifiers, static fn (string $modifier) => $modifier !== 'abstract'));
110
    }
111
112
    /**
113
     * Gets the complete set of parameter configs for a given method reflection.
114
     *
115
     * @param ReflectionMethod $method Reflection of a method.
116
     *
117
     * @return ParameterConfig[] List of parameter configs. The order is maintained.
118
     */
119 12
    private function getMethodParameterConfigs(ReflectionMethod $method): array
120
    {
121 12
        $parameters = [];
122 12
        foreach ($method->getParameters() as $param) {
123 7
            $parameters[$param->getName()] = $this->getMethodParameterConfig($param);
124
        }
125
126 12
        return $parameters;
127
    }
128
129
    /**
130
     * Gets single parameter config for individual method's parameter reflection.
131
     *
132
     * @param ReflectionParameter $param Reflection of a method's parameter.
133
     *
134
     * @return ParameterConfig Single parameter config.
135
     */
136 7
    private function getMethodParameterConfig(ReflectionParameter $param): ParameterConfig
137
    {
138 7
        return new ParameterConfig(
139 7
            type: $this->getMethodParameterTypeConfig($param),
140 7
            name: $param->getName(),
141 7
            isDefaultValueAvailable: $param->isDefaultValueAvailable(),
142 7
            isDefaultValueConstant: $param->isDefaultValueAvailable()
143 2
                ? $param->isDefaultValueConstant()
144 7
                : null,
145 7
            defaultValueConstantName: $param->isOptional()
146 2
                ? $param->getDefaultValueConstantName()
147 7
                : null,
148 7
            defaultValue: $param->isOptional()
149 2
                ? $param->getDefaultValue()
150 7
                : null,
151
        );
152
    }
153
154
    /**
155
     * Gets single type config for individual method's parameter reflection.
156
     *
157
     * @param ReflectionParameter $param Reflection pf a method's parameter.
158
     *
159
     * @return TypeConfig|null Single type config. `null` is returned when type is not specified.
160
     */
161 7
    private function getMethodParameterTypeConfig(ReflectionParameter $param): ?TypeConfig
162
    {
163 7
        $type = $param->getType();
164 7
        if (!$type) {
165 2
            return null;
166
        }
167
168 7
        return new TypeConfig(
169 7
            name: $this->convertTypeToString($type),
170 7
            allowsNull: $type->allowsNull(),
171
        );
172
    }
173
174
    /**
175
     * Gets single return type config for individual method reflection.
176
     *
177
     * @param ReflectionMethod $method Reflection of a method.
178
     *
179
     * @return TypeConfig|null Single type config. `null` is returned when return type is not specified.
180
     */
181 12
    private function getMethodReturnTypeConfig(ReflectionMethod $method): ?TypeConfig
182
    {
183 12
        $returnType = $method->getReturnType();
184 12
        if (!$returnType && method_exists($method, 'getTentativeReturnType')) {
185
            $returnType = $method->getTentativeReturnType();
186
        }
187
188 12
        if (!$returnType) {
189 4
            return null;
190
        }
191
192
        /** @psalm-suppress UndefinedClass Needed for PHP 8.0 only, because ReflectionIntersectionType is not supported. */
193 11
        return new TypeConfig(
194 11
            name: $this->convertTypeToString($returnType),
195 11
            allowsNull: $returnType->allowsNull(),
196
        );
197
    }
198
199
    /**
200
     * @psalm-suppress UndefinedClass Needed for PHP 8.0 only, because ReflectionIntersectionType is not supported.
201
     */
202 11
    private function convertTypeToString(
203
        ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType $type
204
    ): string {
205 11
        if ($type instanceof ReflectionNamedType) {
206 11
            return $type->getName();
207
        }
208
209 6
        if ($type instanceof ReflectionUnionType) {
0 ignored issues
show
introduced by
$type is always a sub-type of ReflectionUnionType.
Loading history...
210 6
            return $this->getUnionType($type);
211
        }
212
213
        return $this->getIntersectionType($type);
214
    }
215
216 6
    private function getUnionType(ReflectionUnionType $type): string
217
    {
218 6
        $types = array_map(
219 6
            static fn (ReflectionNamedType $namedType) => $namedType->getName(),
220 6
            $type->getTypes()
221
        );
222
223 6
        return implode('|', $types);
224
    }
225
226
    /**
227
     * @psalm-suppress UndefinedClass Needed for PHP 8.0 only, because ReflectionIntersectionType is not supported.
228
     */
229
    private function getIntersectionType(ReflectionIntersectionType $type): string
230
    {
231
        $types = array_map(
232
            static fn (ReflectionNamedType $namedType) => $namedType->getName(),
233
            $type->getTypes()
234
        );
235
236
        return implode('&', $types);
237
    }
238
}
239