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

ClassRenderer   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 299
Duplicated Lines 0 %

Test Coverage

Coverage 91.14%

Importance

Changes 7
Bugs 2 Features 0
Metric Value
eloc 74
dl 0
loc 299
ccs 72
cts 79
cp 0.9114
rs 10
c 7
b 2
f 0
wmc 29

16 Methods

Rating   Name   Duplication   Size   Complexity  
A renderClassSignature() 0 6 1
A render() 0 15 3
A renderMethodCallParameters() 0 8 2
A renderReturn() 0 7 2
A renderMethodParameters() 0 12 3
A renderType() 0 7 3
A renderModifiers() 0 3 1
A renderMethodSignature() 0 7 1
A renderReturnType() 0 7 2
A renderParameterDefaultValue() 0 11 3
A renderClassBody() 0 3 1
A renderIndent() 0 3 1
A renderMethodParameter() 0 11 2
A renderMethod() 0 9 1
A renderMethodBody() 0 9 1
A renderMethods() 0 8 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Proxy;
6
7
use InvalidArgumentException;
8
use Yiisoft\Proxy\Config\ClassConfig;
9
use Yiisoft\Proxy\Config\MethodConfig;
10
use Yiisoft\Proxy\Config\ParameterConfig;
11
use Yiisoft\Proxy\Config\TypeConfig;
12
13
/**
14
 * Renders class contents based on a given config ({@see ClassConfig}).
15
 */
16
final class ClassRenderer
17
{
18
    /**
19
     * @var string A template for rendering class signature.
20
     *
21
     * @see renderClassSignature()
22
     */
23
    private string $classSignatureTemplate = '{{modifiers}}class {{name}} extends {{parent}}';
24
    /**
25
     * @var string A template for rendering proxy method signature.
26
     *
27
     * @see renderMethodSignature()
28
     */
29
    private string $proxyMethodSignatureTemplate = '{{modifiers}} function {{name}}({{params}}){{returnType}}';
30
    /**
31
     * @var string A template for rendering proxy method body.
32
     *
33
     * @see renderMethodBody()
34
     */
35
    private string $proxyMethodBodyTemplate = '{{return}}$this->call({{methodName}}, [{{params}}]);';
36
37
    /**
38
     * Renders class contents to a string.
39
     *
40
     * @param ClassConfig $classConfig Class config.
41
     *
42
     * @return string Class contents as a string, opening PHP tag is not included.
43
     */
44 9
    public function render(ClassConfig $classConfig): string
45
    {
46 9
        if ($classConfig->isInterface) {
47 1
            throw new InvalidArgumentException('Rendering of interfaces is not supported.');
48
        }
49
50 8
        if (!$classConfig->parent) {
51 1
            throw new InvalidArgumentException('Class config is missing a parent.');
52
        }
53
54 7
        return trim($this->renderClassSignature($classConfig))
55
            . "\n"
56
            . '{'
57 7
            . $this->renderClassBody($classConfig)
58
            . '}';
59
    }
60
61
    /**
62
     * Renders class signature using {@see $classSignatureTemplate}.
63
     *
64
     * @param ClassConfig $classConfig Class config.
65
     *
66
     * @return string Class signature as a string.
67
     */
68 7
    private function renderClassSignature(ClassConfig $classConfig): string
69
    {
70 7
        return strtr($this->classSignatureTemplate, [
71 7
            '{{modifiers}}' => $this->renderModifiers($classConfig->modifiers),
72 7
            '{{name}}' => $classConfig->shortName,
73 7
            '{{parent}}' => $classConfig->parent,
74
        ]);
75
    }
76
77
    /**
78
     * Renders modifiers section.
79
     *
80
     * @param string[] $modifiers A list of modifiers
81
     *
82
     * @return string Modifiers section as a string.
83
     *
84
     * @see ClassConfig::$modifiers
85
     */
86 7
    private function renderModifiers(array $modifiers): string
87
    {
88 7
        return implode(' ', $modifiers);
89
    }
90
91
    /**
92
     * Renders class body.
93
     *
94
     * @param ClassConfig $classConfig Class config.
95
     *
96
     * @return string Class body as a string. Empty string is returned when class config has no methods.
97
     */
98 7
    private function renderClassBody(ClassConfig $classConfig): string
99
    {
100 7
        return $this->renderMethods($classConfig->methods);
101
    }
102
103
    /**
104
     * Renders all methods.
105
     *
106
     * @param MethodConfig[] $methods A list of method configs.
107
     *
108
     * @return string Methods' sequence as a string. Empty string is returned when no methods were passed.
109
     *
110
     * @see ClassConfig::$methods
111
     */
112 7
    private function renderMethods(array $methods): string
113
    {
114 7
        $methodsCode = '';
115 7
        foreach ($methods as $method) {
116 7
            $methodsCode .= "\n" . $this->renderMethod($method);
117
        }
118
119 7
        return $methodsCode;
120
    }
121
122
    /**
123
     * Renders a single  method.
124
     *
125
     * @param MethodConfig $method Method config.
126
     *
127
     * @return string Method as a string.
128
     */
129 7
    private function renderMethod(MethodConfig $method): string
130
    {
131 7
        return $this->renderMethodSignature($method)
132 7
            . "\n" . $this->renderIndent()
133
            . '{'
134 7
            . $this->renderMethodBody($method)
135 7
            . $this->renderIndent()
136
            . '}'
137
            . "\n";
138
    }
139
140
    /**
141
     * Renders a proxy method signature using {@see $proxyMethodSignatureTemplate}.
142
     *
143
     * @param MethodConfig $method Method config.
144
     *
145
     * @return string Method signature as a string.
146
     */
147 7
    private function renderMethodSignature(MethodConfig $method): string
148
    {
149 7
        return strtr($this->proxyMethodSignatureTemplate, [
150 7
            '{{modifiers}}' => $this->renderIndent() . $this->renderModifiers($method->modifiers),
151 7
            '{{name}}' => $method->name,
152 7
            '{{params}}' => $this->renderMethodParameters($method->parameters),
153 7
            '{{returnType}}' => $this->renderReturnType($method),
154
        ]);
155
    }
156
157
    /**
158
     * Renders all parameters for a method.
159
     *
160
     * @param ParameterConfig[] $parameters A list of parameter configs.
161
     *
162
     * @return string Method parameters as a string. Empty string is returned when no parameters were passed.
163
     */
164 7
    private function renderMethodParameters(array $parameters): string
165
    {
166 7
        $params = '';
167 7
        foreach ($parameters as $index => $parameter) {
168 2
            $params .= $this->renderMethodParameter($parameter) ;
169
170 2
            if ($index !== array_key_last($parameters)) {
171
                $params .= ', ';
172
            }
173
        }
174
175 7
        return $params;
176
    }
177
178
    /**
179
     * Renders a single parameter for a method.
180
     *
181
     * @param ParameterConfig $parameter Parameter config.
182
     *
183
     * @return string Method parameter as a string.
184
     */
185 2
    private function renderMethodParameter(ParameterConfig $parameter): string
186
    {
187 2
        $type = $parameter->hasType()
188 2
            ? $this->renderType($parameter->type)
189
            : '';
190 2
        $output = $type
191
            . ' $'
192 2
            . $parameter->name
193 2
            . $this->renderParameterDefaultValue($parameter);
194
195 2
        return ltrim($output);
196
    }
197
198
    /**
199
     * Renders default value for a parameter. Equal sign (surrounded with spaces) is included.
200
     *
201
     * @param ParameterConfig $parameter Parameter config.
202
     *
203
     * @return string Parameter's default value as a string. Empty string is returned when no default value was
204
     * specified.
205
     */
206 2
    private function renderParameterDefaultValue(ParameterConfig $parameter): string
207
    {
208 2
        if (!$parameter->isDefaultValueAvailable) {
209 2
            return '';
210
        }
211
212
        $value = $parameter->isDefaultValueConstant
213
            ? $parameter->defaultValueConstantName
214
            : var_export($parameter->defaultValue, true);
215
216
        return ' = ' . $value;
217
    }
218
219
    /**
220
     * Renders a proxy method's body using {@see $proxyMethodBodyTemplate}.
221
     *
222
     * @param MethodConfig $method Method config.
223
     *
224
     * @return string Method body as a string.
225
     */
226 7
    private function renderMethodBody(MethodConfig $method): string
227
    {
228 7
        $output = strtr($this->proxyMethodBodyTemplate, [
229 7
            '{{return}}' => $this->renderIndent(2) . $this->renderReturn($method),
230 7
            '{{methodName}}' => "'" . $method->name . "'",
231 7
            '{{params}}' => $this->renderMethodCallParameters($method->parameters),
232
        ]);
233
234 7
        return "\n" . $output . "\n";
235
    }
236
237
    /**
238
     * Renders return statement for a method.
239
     *
240
     * @param MethodConfig $method Method config.
241
     *
242
     * @return string Return statement as a string. Empty string is returned when no return type was specified or it was
243
     * explicitly specified as `void`.
244
     */
245 7
    private function renderReturn(MethodConfig $method): string
246
    {
247 7
        if ($method->returnType?->name === 'void') {
248 2
            return '';
249
        }
250
251 6
        return 'return ';
252
    }
253
254
    /**
255
     * Renders return type for a method.
256
     *
257
     * @param MethodConfig $method Method config.
258
     *
259
     * @return string Return type as a string. Empty string is returned when method has no return type.
260
     */
261 7
    private function renderReturnType(MethodConfig $method): string
262
    {
263 7
        if (!$method->hasReturnType()) {
264 1
            return '';
265
        }
266
267 6
        return ': ' . $this->renderType($method->returnType);
268
    }
269
270
    /**
271
     * Renders a type. Nullability is handled too.
272
     *
273
     * @param TypeConfig $type Type config.
274
     *
275
     * @return string Type as a string.
276
     */
277 6
    private function renderType(TypeConfig $type): string
278
    {
279 6
        if ($type->name === 'mixed' || !$type->allowsNull) {
280 6
            return $type->name;
281
        }
282
283
        return '?' . $type->name;
284
    }
285
286
    /**
287
     * Renders parameters passed to a proxy's method call.
288
     *
289
     * @param ParameterConfig[] $parameters A map where key is a {@see ParameterConfig::$name} and value is
290
     * {@see ParameterConfig} instance.
291
     * @psalm-param array<string, ParameterConfig> $parameters
292
     *
293
     * @return string Parameters as a string. Empty string is returned when no parameters were passed.
294
     */
295 7
    private function renderMethodCallParameters(array $parameters): string
296
    {
297 7
        $keys = array_keys($parameters);
298 7
        if ($keys === []) {
299 7
            return '';
300
        }
301
302 2
        return '$' . implode(', $', $keys);
303
    }
304
305
    /**
306
     * Renders indent. 4 spaces are used, with no tabs.
307
     *
308
     * @param int $count How many times indent should be repeated.
309
     *
310
     * @return string Indent as a string.
311
     */
312 7
    private function renderIndent(int $count = 1): string
313
    {
314 7
        return str_repeat('    ', $count);
315
    }
316
}
317