Lines
100.00%
36 / 36
Functions and Methods
100.00%
3 / 3
Classes and Traits
100.00%
1 / 1
| Name | Lines | Functions and Methods | CRAP | Classes and Traits | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| MessageSerializer | 100.00% | 36 / 36 | 100.00% | 3 / 3 | 18 | 100.00% | 1 / 1 | |||
| serialize | 100.00% | 12 / 12 | 100.00% | 1 / 1 | 2 | |||||
| unserialize | 100.00% | 23 / 23 | 100.00% | 1 / 1 | 15 | |||||
| describe | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| 1 | <?php | |
| 2 | ||
| 3 | declare(strict_types=1); | |
| 4 | ||
| 5 | namespace Tomaj\Hermes; | |
| 6 | ||
| 7 | use JsonException; | |
| 8 | ||
| 9 | /** | |
| 10 | * Exception messages never contain the serialized string: it may hold personal data or tokens and | |
| 11 | * would end up in logs and error trackers. They name the problem, the length and a short SHA-256 | |
| 12 | * prefix that matches the stored message (`hash('sha256', $string)`). | |
| 13 | */ | |
| 14 | final class MessageSerializer implements SerializerInterface | |
| 15 | { | |
| 16 | #[\Override] | |
| 17 | public function serialize(MessageInterface $message): string | |
| 18 | { | |
| 19 | try { | |
| 20 | return json_encode([ | |
| 21 | 'message' => [ | |
| 22 | 'id' => $message->getId(), | |
| 23 | 'type' => $message->getType(), | |
| 24 | 'created' => $message->getCreated(), | |
| 25 | 'payload' => $message->getPayload(), | |
| 26 | 'execute_at' => $message->getExecuteAt(), | |
| 27 | 'retries' => $message->getRetries(), | |
| 28 | ], | |
| 29 | ], JSON_INVALID_UTF8_IGNORE | JSON_THROW_ON_ERROR); | |
| 30 | } catch (JsonException $e) { | |
| 31 | throw new SerializeException("Cannot serialize message {$message->getId()}: {$e->getMessage()}", $e->getCode(), previous: $e); | |
| 32 | } | |
| 33 | } | |
| 34 | ||
| 35 | #[\Override] | |
| 36 | public function unserialize(string $string): MessageInterface | |
| 37 | { | |
| 38 | try { | |
| 39 | $data = json_decode($string, true, flags: JSON_THROW_ON_ERROR); | |
| 40 | } catch (JsonException $e) { | |
| 41 | throw new SerializeException('Cannot unserialize message: invalid JSON (' . $e->getMessage() . ') ' . $this->describe($string), $e->getCode(), previous: $e); | |
| 42 | } | |
| 43 | if (!is_array($data) || !isset($data['message'])) { | |
| 44 | throw new SerializeException('Cannot unserialize message: no "message" key ' . $this->describe($string)); | |
| 45 | } | |
| 46 | $message = $data['message']; | |
| 47 | if (!is_array($message) || !isset($message['type'], $message['id'], $message['created'])) { | |
| 48 | throw new SerializeException('Invalid message format: "message" needs type, id and created ' . $this->describe($string)); | |
| 49 | } | |
| 50 | ||
| 51 | $type = $message['type']; | |
| 52 | $id = $message['id']; | |
| 53 | $created = $message['created']; | |
| 54 | if (!is_string($type) || !is_string($id) || !is_numeric($created)) { | |
| 55 | throw new SerializeException('Invalid message format: type and id must be strings and created a number ' . $this->describe($string)); | |
| 56 | } | |
| 57 | ||
| 58 | $executeAt = null; | |
| 59 | if (isset($message['execute_at']) && is_numeric($message['execute_at'])) { | |
| 60 | $executeAt = (float) $message['execute_at']; | |
| 61 | } | |
| 62 | ||
| 63 | $retries = 0; | |
| 64 | if (isset($message['retries']) && is_numeric($message['retries'])) { | |
| 65 | $retries = (int) $message['retries']; | |
| 66 | } | |
| 67 | ||
| 68 | $payload = null; | |
| 69 | if (isset($message['payload']) && is_array($message['payload'])) { | |
| 70 | $payload = $message['payload']; | |
| 71 | } | |
| 72 | ||
| 73 | return new Message($type, $payload, $id, (float) $created, $executeAt, $retries); | |
| 74 | } | |
| 75 | ||
| 76 | private function describe(string $string): string | |
| 77 | { | |
| 78 | return '(' . strlen($string) . ' bytes, sha256 ' . substr(hash('sha256', $string), 0, 16) . ')'; | |
| 79 | } | |
| 80 | } |