Passed
Push — master ( 44c768...c7663e )
by
unknown
41s queued 11s
created

ClassConfigFactory::getMethodModifiers()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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