Completed
Push — master ( dae852...0552f9 )
by Frank
03:33
created

CodeDumper::dumpCommandConstructor()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

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