Passed
Pull Request — master (#54)
by Frank
03:17 queued 01:08
created

CodeDumper::dumpEvents()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

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