ClassRenderer   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 328
Duplicated Lines 0 %

Test Coverage

Coverage 92.93%

Importance

Changes 7
Bugs 2 Features 0
Metric Value
eloc 84
c 7
b 2
f 0
dl 0
loc 328
ccs 92
cts 99
cp 0.9293
rs 9.76
wmc 33

17 Methods

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