JsonCommandSerializer::getCommandDefinition()   B
last analyzed

Complexity

Conditions 9
Paths 4

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 15
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 28
rs 8.0555
1
<?php
2
3
/*
4
 * cqrs-async (https://github.com/phpgears/cqrs-async).
5
 * Async decorator for CQRS command bus.
6
 *
7
 * @license MIT
8
 * @link https://github.com/phpgears/cqrs-async
9
 * @author Julián Gutiérrez <[email protected]>
10
 */
11
12
declare(strict_types=1);
13
14
namespace Gears\CQRS\Async\Serializer;
15
16
use Gears\CQRS\Async\Serializer\Exception\CommandSerializationException;
17
use Gears\CQRS\Command;
18
19
final class JsonCommandSerializer implements CommandSerializer
20
{
21
    /**
22
     * JSON encoding options.
23
     * Preserve float values and encode &, ', ", < and > characters in the resulting JSON.
24
     */
25
    private const JSON_ENCODE_OPTIONS = \JSON_UNESCAPED_UNICODE
26
        | \JSON_UNESCAPED_SLASHES
27
        | \JSON_PRESERVE_ZERO_FRACTION
28
        | \JSON_HEX_AMP
29
        | \JSON_HEX_APOS
30
        | \JSON_HEX_QUOT
31
        | \JSON_HEX_TAG;
32
33
    /**
34
     * JSON decoding options.
35
     * Decode large integers as string values.
36
     */
37
    private const JSON_DECODE_OPTIONS = \JSON_BIGINT_AS_STRING;
38
39
    /**
40
     * {@inheritdoc}
41
     */
42
    public function serialize(Command $command): string
43
    {
44
        $serialized = \json_encode(
45
            [
46
                'class' => \get_class($command),
47
                'payload' => $command->getPayload(),
48
            ],
49
            static::JSON_ENCODE_OPTIONS
50
        );
51
52
        // @codeCoverageIgnoreStart
53
        if ($serialized === false || \json_last_error() !== \JSON_ERROR_NONE) {
54
            throw new CommandSerializationException(\sprintf(
55
                'Error serializing command %s due to %s.',
56
                \get_class($command),
57
                \lcfirst(\json_last_error_msg())
58
            ));
59
        }
60
        // @codeCoverageIgnoreEnd
61
62
        return $serialized;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function fromSerialized(string $serialized): Command
69
    {
70
        ['class' => $commandClass, 'payload' => $payload] = $this->getCommandDefinition($serialized);
71
72
        if (!\class_exists($commandClass)) {
73
            throw new CommandSerializationException(\sprintf('Command class %s cannot be found.', $commandClass));
74
        }
75
76
        if (!\in_array(Command::class, \class_implements($commandClass), true)) {
77
            throw new CommandSerializationException(\sprintf(
78
                'Command class must implement %s, %s given.',
79
                Command::class,
80
                $commandClass
81
            ));
82
        }
83
84
        // @codeCoverageIgnoreStart
85
        try {
86
            /* @var Command $commandClass */
87
            return $commandClass::reconstitute($payload);
88
        } catch (\Exception $exception) {
89
            throw new CommandSerializationException('Error reconstituting command.', 0, $exception);
90
        }
91
        // @codeCoverageIgnoreEnd
92
    }
93
94
    /**
95
     * Get command definition from serialization.
96
     *
97
     * @param string $serialized
98
     *
99
     * @throws CommandSerializationException
100
     *
101
     * @return array<string, mixed>
102
     */
103
    private function getCommandDefinition(string $serialized): array
104
    {
105
        if (\trim($serialized) === '') {
106
            throw new CommandSerializationException('Malformed JSON serialized command: empty string.');
107
        }
108
109
        $definition = \json_decode($serialized, true, 512, static::JSON_DECODE_OPTIONS);
110
111
        // @codeCoverageIgnoreStart
112
        if ($definition === null || \json_last_error() !== \JSON_ERROR_NONE) {
113
            throw new CommandSerializationException(\sprintf(
114
                'Command deserialization failed due to error %s: %s.',
115
                \json_last_error(),
116
                \lcfirst(\json_last_error_msg())
117
            ));
118
        }
119
        // @codeCoverageIgnoreEnd
120
121
        if (!\is_array($definition)
122
            || !isset($definition['class'], $definition['payload'])
123
            || \count(\array_diff(\array_keys($definition), ['class', 'payload'])) !== 0
124
            || !\is_string($definition['class'])
125
            || !\is_array($definition['payload'])
126
        ) {
127
            throw new CommandSerializationException('Malformed JSON serialized command.');
128
        }
129
130
        return $definition;
131
    }
132
}
133