Completed
Pull Request — master (#54)
by Frank
10:20 queued 07:56
created

CodeDumper::dumpConstructor()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 16
c 2
b 0
f 0
nc 3
nop 1
dl 0
loc 24
ccs 13
cts 13
cp 1
crap 3
rs 9.7333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EventSauce\EventSourcing\CodeGeneration;
6
7
use LogicException;
8
use const null;
9
use function array_filter;
10
use function sprintf;
11
use function ucfirst;
12
use function var_export;
13
14
class CodeDumper
15
{
16
    /**
17
     * @var DefinitionGroup
18
     */
19
    private $definitionGroup;
20
21 12
    public function dump(DefinitionGroup $definitionGroup, bool $withHelpers = true, bool $withSerialization = true): string
22
    {
23 12
        $this->definitionGroup = $definitionGroup;
24 12
        $definitionCode = $this->dumpClass($definitionGroup->events(), $withHelpers, $withSerialization);
25 12
        $commandCode = $this->dumpClass($definitionGroup->commands(), $withHelpers, $withSerialization);
26 11
        $namespace = $definitionGroup->namespace();
27 11
        $allCode = implode("\n\n", array_filter([$definitionCode, $commandCode]));
28
29 11
        if ($withSerialization) {
30 11
            $namespace .= ";
31
32
use EventSauce\EventSourcing\Serialization\SerializablePayload";
33
        }
34
35
        return <<<EOF
36
<?php
37
38 11
declare(strict_types=1);
39
40 11
namespace $namespace;
41
42
$allCode
43
44
EOF;
45 12
    }
46
47 12
    private function dumpClass(array $definitions, bool $withHelpers, bool $withSerialization): string
48
    {
49 12
        $code = [];
50 2
51
        if (empty($definitions)) {
52
            return '';
53 10
        }
54 10
55 10
        foreach ($definitions as $definition) {
56 10
            $name = $definition->name();
57 10
            $fields = $this->dumpFields($definition);
58 10
            $constructor = $this->dumpConstructor($definition);
59 10
            $methods = $this->dumpMethods($definition);
60 10
            $deserializer = $this->dumpSerializationMethods($definition);
61
            $testHelpers = $withHelpers ? $this->dumpTestHelpers($definition) : '';
62 10
            $implements = $withSerialization ? ' implements SerializablePayload' : '';
63 10
64
            $code[] = <<<EOF
65 10
final class $name$implements
66
{
67 10
$fields$constructor$methods$deserializer
68
69
$testHelpers}
70
71
72
EOF;
73 10
        }
74
75
        return rtrim(implode('', $code));
76 12
    }
77
78 12
    private function dumpFields(PayloadDefinition $definition): string
79 11
    {
80 11
        $fields = $this->fieldsFromDefinition($definition);
81
        $code = [];
82
        $code[] = <<<EOF
83
84 11
EOF;
85 10
86 10
        foreach ($fields as $field) {
87
            $name = $field['name'];
88 10
            $type = $this->definitionGroup->resolveTypeAlias($field['type']);
89
90 10
            $code[] = <<<EOF
91
    /**
92 10
     * @var $type
93
     */
94
    private \$$name;
95
96
97
EOF;
98 11
        }
99
100
        return implode('', $code);
101 11
    }
102
103 11
    private function dumpConstructor(PayloadDefinition $definition): string
104 11
    {
105 11
        $arguments = [];
106
        $assignments = [];
107 11
        $fields = $this->fieldsFromDefinition($definition);
108 1
109
        if (empty($fields)) {
110
            return '';
111 10
        }
112 10
113 10
        foreach ($fields as $field) {
114 10
            $resolvedType = $this->definitionGroup->resolveTypeAlias($field['type']);
115
            $arguments[] = sprintf('        %s $%s', $resolvedType, $field['name']);
116
            $assignments[] = sprintf('        $this->%s = $%s;', $field['name'], $field['name']);
117 10
        }
118 10
119
        $arguments = implode(",\n", $arguments);
120
        $assignments = implode("\n", $assignments);
121
122 10
        return <<<EOF
123
    public function __construct(
124 10
$arguments
125
    ) {
126
$assignments
127
    }
128
129
130
EOF;
131 11
    }
132
133 11
    private function dumpMethods(PayloadDefinition $command): string
134
    {
135 11
        $methods = [];
136 10
137 10
        foreach ($this->fieldsFromDefinition($command) as $field) {
138
            $methods[] = <<<EOF
139 10
    public function {$field['name']}(): {$this->definitionGroup->resolveTypeAlias($field['type'])}
140
    {
141
        return \$this->{$field['name']};
142
    }
143
144
145
EOF;
146 11
        }
147
148
        return empty($methods) ? '' : rtrim(implode('', $methods)) . "\n\n";
149 10
    }
150
151 10
    private function dumpSerializationMethods(PayloadDefinition $definition)
152 10
    {
153 10
        $name = $definition->name();
154
        $arguments = [];
155 10
        $serializers = [];
156 9
157 9
        foreach ($this->fieldsFromDefinition($definition) as $field) {
158 9
            $type = $this->definitionGroup->resolveTypeAlias($field['type']);
159 9
            $parameter = sprintf('$payload[\'%s\']', $field['name']);
160 9
            $template = $definition->deserializerForField($field['name'])
161
                ?: $definition->deserializerForType($field['type']);
162 9
            $arguments[] = trim(strtr($template, ['{type}' => $type, '{param}' => $parameter]));
163 9
164 9
            $property = sprintf('$this->%s', $field['name']);
165 9
            $template = $definition->serializerForField($field['name'])
166 9
                ?: $definition->serializerForType($field['type']);
167
            $template = sprintf("'%s' => %s", $field['name'], $template);
168
            $serializers[] = trim(strtr($template, ['{type}' => $type, '{param}' => $property]));
169 10
        }
170
171 10
        $arguments = preg_replace('/^.{2,}$/m', '            $0', implode(",\n", $arguments));
172 9
173
        if ( ! empty($arguments)) {
174
            $arguments = "\n$arguments\n        ";
175 10
        }
176
177 10
        $serializers = preg_replace('/^.{2,}$/m', '            $0', implode(",\n", $serializers));
178 9
179
        if ( ! empty($serializers)) {
180
            $serializers = "\n$serializers,\n        ";
181
        }
182
183
        return <<<EOF
184 10
    public static function fromPayload(array \$payload): SerializablePayload
185
    {
186
        return new $name($arguments);
187
    }
188
189 10
    public function toPayload(): array
190
    {
191
        return [$serializers];
192
    }
193
EOF;
194 8
    }
195
196 8
    private function dumpTestHelpers(PayloadDefinition $definition): string
197 8
    {
198 8
        $constructor = [];
199 8
        $constructorArguments = '';
200
        $constructorValues = [];
201 8
        $helpers = [];
202 7
203
        foreach ($this->fieldsFromDefinition($definition) as $field) {
204 7
            $resolvedType = $this->definitionGroup->resolveTypeAlias($field['type']);
205 3
206
            if (null === $field['example']) {
207 3
                $constructor[] = ucfirst($field['name']);
208 1
209
                if ('' !== $constructorArguments) {
210
                    $constructorArguments .= ', ';
211 3
                }
212 3
213
                $constructorArguments .= sprintf('%s $%s', $resolvedType, $field['name']);
214 5
                $constructorValues[] = sprintf('$%s', $field['name']);
215 5
            } else {
216 5
                $constructorValues[] = $this->dumpConstructorValue($field, $definition);
217
                $method = sprintf('with%s', ucfirst($field['name']));
218
                $helpers[] = <<<EOF
219
    /**
220 5
     * @codeCoverageIgnore
221
     */
222
    public function $method({$resolvedType} \${$field['name']}): {$definition->name()}
223 5
    {
224
        \$clone = clone \$this;
225
        \$clone->{$field['name']} = \${$field['name']};
226
227
        return \$clone;
228
    }
229
230
231
EOF;
232
            }
233 8
        }
234 8
235
        $constructor = sprintf('with%s', implode('And', $constructor));
236 8
        $constructorValues = implode(",\n            ", $constructorValues);
237 7
238
        if ('' !== $constructorValues) {
239
            $constructorValues = "\n            $constructorValues\n        ";
240 8
        }
241
242
        $helpers[] = <<<EOF
243
    /**
244 8
     * @codeCoverageIgnore
245
     */
246 8
    public static function $constructor($constructorArguments): {$definition->name()}
247
    {
248
        return new {$definition->name()}($constructorValues);
249
    }
250
251
252 8
EOF;
253
254
        return rtrim(implode('', $helpers)) . "\n";
255 5
    }
256
257 5
    private function dumpConstructorValue(array $field, PayloadDefinition $definition): string
258 5
    {
259
        $parameter = rtrim($field['example']);
260 5
        $resolvedType = $this->definitionGroup->resolveTypeAlias($field['type']);
261 4
262
        if (gettype($parameter) === $resolvedType) {
263
            $parameter = var_export($parameter, true);
264 5
        }
265 5
266
        $template = $definition->deserializerForField($field['name'])
267 5
            ?: $definition->deserializerForType($field['type']);
268
269
        return rtrim(strtr($template, ['{type}' => $resolvedType, '{param}' => $parameter]));
270
    }
271
272
    /**
273
     * @param PayloadDefinition $definition
274
     *
275 12
     * @return array
276
     */
277 12
    private function fieldsFromDefinition(PayloadDefinition $definition): array
278
    {
279 12
        $fields = $this->fieldsFrom($definition->fieldsFrom());
280 4
281 5
        foreach ($definition->fields() as $field) {
282
            array_push($fields, $field);
283 5
        }
284
285
        return $fields;
286
    }
287
288
    private function fieldsFrom(string $fieldsFrom): array
289 11
    {
290
        if (empty($fieldsFrom)) {
291
            return [];
292
        }
293
294
        foreach ($this->definitionGroup->events() as $definition) {
295
            if ($definition->name() === $fieldsFrom) {
296
                return $definition->fields();
297 12
            }
298
        }
299 12
300
        foreach ($this->definitionGroup->commands() as $command) {
301 11
            if ($command->name() === $fieldsFrom) {
302 10
                return $command->fields();
303
            }
304
        }
305 11
306
        throw new LogicException("Could not inherit fields from {$fieldsFrom}.");
307
    }
308
}
309