Completed
Push — master ( 974844...187cbb )
by Frank
10s
created

CodeDumper::dump()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 13
nc 2
nop 3
dl 0
loc 20
ccs 10
cts 10
cp 1
crap 2
rs 9.4285
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 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 5
    public function dump(DefinitionGroup $definitionGroup, bool $withHelpers = true, bool $withSerialization = true): string
23
    {
24 5
        $this->definitionGroup = $definitionGroup;
25 5
        $eventsCode = $this->dumpEvents($definitionGroup->events(), $withHelpers, $withSerialization);
26 5
        $commandCode = $this->dumpCommands($definitionGroup->commands());
27 4
        $namespace = $definitionGroup->namespace();
28 4
        $allCode = implode(array_filter([$eventsCode, $commandCode]), "\n\n");
0 ignored issues
show
Unused Code introduced by
The call to implode() has too many arguments starting with ' '. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

28
        $allCode = /** @scrutinizer ignore-call */ implode(array_filter([$eventsCode, $commandCode]), "\n\n");

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

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