Test Failed
Pull Request — master (#49)
by
unknown
10:22 queued 08:02
created

ClassRenderer::renderImplements()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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