Test Failed
Pull Request — master (#29)
by Frank
02:39
created

CodeDumper   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 310
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 4
dl 0
loc 310
ccs 131
cts 131
cp 1
rs 8.2857
c 0
b 0
f 0

11 Methods

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