Completed
Push — master ( cf6c6d...e2d72b )
by Julián
01:21
created

JsonEventSerializer   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 19
lcom 1
cbo 1
dl 0
loc 165
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A serialize() 0 23 3
A getSerializationAttributes() 0 6 1
A fromSerialized() 0 26 4
A getEventDefinition() 0 15 6
A getDeserializationDefinition() 0 20 4
A getDeserializationAttributes() 0 6 1
1
<?php
2
3
/*
4
 * event-async (https://github.com/phpgears/event-async).
5
 * Async decorator for Event bus.
6
 *
7
 * @license MIT
8
 * @link https://github.com/phpgears/event-async
9
 * @author Julián Gutiérrez <[email protected]>
10
 */
11
12
declare(strict_types=1);
13
14
namespace Gears\Event\Async\Serializer;
15
16
use Gears\Event\Async\Serializer\Exception\EventSerializationException;
17
use Gears\Event\Event;
18
19
class JsonEventSerializer implements EventSerializer
20
{
21
    /**
22
     * JSON encoding options.
23
     * Preserve float values and encode &, ', ", < and > characters in the resulting JSON.
24
     */
25
    protected 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
    protected const JSON_DECODE_OPTIONS = \JSON_BIGINT_AS_STRING;
38
39
    /**
40
     * \DateTime::RFC3339_EXTENDED cannot handle microseconds on \DateTimeImmutable::createFromFormat.
41
     *
42
     * @see https://stackoverflow.com/a/48949373
43
     */
44
    protected const DATE_RFC3339_EXTENDED = 'Y-m-d\TH:i:s.uP';
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    final public function serialize(Event $event): string
50
    {
51
        $serialized = \json_encode(
52
            [
53
                'class' => \get_class($event),
54
                'payload' => $event->getPayload(),
55
                'attributes' => $this->getSerializationAttributes($event),
56
            ],
57
            static::JSON_ENCODE_OPTIONS
58
        );
59
60
        // @codeCoverageIgnoreStart
61
        if ($serialized === false || \json_last_error() !== \JSON_ERROR_NONE) {
62
            throw new EventSerializationException(\sprintf(
63
                'Error serializing event %s due to %s',
64
                \get_class($event),
65
                \lcfirst(\json_last_error_msg())
66
            ));
67
        }
68
        // @codeCoverageIgnoreEnd
69
70
        return $serialized;
71
    }
72
73
    /**
74
     * Get serialization attributes.
75
     *
76
     * @param Event $event
77
     *
78
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
79
     */
80
    protected function getSerializationAttributes(Event $event): array
81
    {
82
        return [
83
            'createdAt' => $event->getCreatedAt()->format(self::DATE_RFC3339_EXTENDED),
84
        ];
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    final public function fromSerialized(string $serialized): Event
91
    {
92
        ['class' => $eventClass, 'payload' => $payload, 'attributes' => $attributes] =
0 ignored issues
show
Bug introduced by
The variable $eventClass does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $payload does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $attributes does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
93
            $this->getEventDefinition($serialized);
94
95
        if (!\class_exists($eventClass)) {
96
            throw new EventSerializationException(\sprintf('Event class %s cannot be found', $eventClass));
97
        }
98
99
        if (!\in_array(Event::class, \class_implements($eventClass), true)) {
100
            throw new EventSerializationException(\sprintf(
101
                'Event class must implement %s, %s given',
102
                Event::class,
103
                $eventClass
104
            ));
105
        }
106
107
        // @codeCoverageIgnoreStart
108
        try {
109
            /* @var Event $eventClass */
110
            return $eventClass::reconstitute($payload, $this->getDeserializationAttributes($attributes));
111
        } catch (\Exception $exception) {
112
            throw new EventSerializationException('Error reconstituting event', 0, $exception);
113
        }
114
        // @codeCoverageIgnoreEnd
115
    }
116
117
    /**
118
     * Get event definition from serialization.
119
     *
120
     * @param string $serialized
121
     *
122
     * @throws EventSerializationException
123
     *
124
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
125
     */
126
    private function getEventDefinition(string $serialized): array
127
    {
128
        $definition = $this->getDeserializationDefinition($serialized);
129
130
        if (!isset($definition['class'], $definition['payload'], $definition['attributes'])
131
            || \count(\array_diff(\array_keys($definition), ['class', 'payload', 'attributes'])) !== 0
132
            || !\is_string($definition['class'])
133
            || !\is_array($definition['payload'])
134
            || !\is_array($definition['attributes'])
135
        ) {
136
            throw new EventSerializationException('Malformed JSON serialized event');
137
        }
138
139
        return $definition;
140
    }
141
142
    /**
143
     * Get deserialization definition.
144
     *
145
     * @param string $serialized
146
     *
147
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
148
     */
149
    private function getDeserializationDefinition(string $serialized): array
150
    {
151
        if (\trim($serialized) === '') {
152
            throw new EventSerializationException('Malformed JSON serialized event: empty string');
153
        }
154
155
        $definition = \json_decode($serialized, true, 512, static::JSON_DECODE_OPTIONS);
156
157
        // @codeCoverageIgnoreStart
158
        if ($definition === null || \json_last_error() !== \JSON_ERROR_NONE) {
159
            throw new EventSerializationException(\sprintf(
160
                'Event deserialization failed due to error %s: %s',
161
                \json_last_error(),
162
                \lcfirst(\json_last_error_msg())
163
            ));
164
        }
165
        // @codeCoverageIgnoreEnd
166
167
        return $definition;
168
    }
169
170
    /**
171
     * Get deserialization attributes.
172
     *
173
     * @param array<string, mixed> $attributes
174
     *
175
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
176
     */
177
    protected function getDeserializationAttributes(array $attributes): array
178
    {
179
        return [
180
            'createdAt' => \DateTimeImmutable::createFromFormat(self::DATE_RFC3339_EXTENDED, $attributes['createdAt']),
181
        ];
182
    }
183
}
184