Completed
Pull Request — master (#11)
by Frank
02:31
created

CodeDumper::fieldsFrom()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

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