Passed
Pull Request — master (#39)
by
unknown
03:06
created

ClassRenderer   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 321
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 2 Features 0
Metric Value
eloc 82
c 6
b 2
f 0
dl 0
loc 321
ccs 86
cts 86
cp 1
rs 9.92
wmc 31

17 Methods

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