Test Failed
Pull Request — master (#49)
by
unknown
10:22 queued 08:02
created

ClassConfigFactory::getMethodConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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