Passed
Push — master ( 7c747f...11d3a2 )
by
unknown
03:14 queued 59s
created

ClassRenderer   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 318
Duplicated Lines 0 %

Test Coverage

Coverage 91.67%

Importance

Changes 7
Bugs 2 Features 0
Metric Value
eloc 78
c 7
b 2
f 0
dl 0
loc 318
ccs 77
cts 84
cp 0.9167
rs 9.92
wmc 31

17 Methods

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