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