ClassConfigFactory   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Test Coverage

Coverage 91.3%

Importance

Changes 10
Bugs 2 Features 0
Metric Value
eloc 74
c 10
b 2
f 0
dl 0
loc 244
ccs 84
cts 92
cp 0.913
rs 10
wmc 24

11 Methods

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