Passed
Push — master ( 184cb3...5f1713 )
by Waaaaaaaaaa
02:19
created

Payload::pushException()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 13
ccs 0
cts 9
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Logfile;
4
5
use Throwable;
6
7
class Payload
8
{
9
    protected $message;
10
11
    protected $config;
12
13
    protected $id;
14
15
    protected $context;
16
17
    protected $extra = [];
18
19
    protected $exceptions = [];
20
21
    public function __construct(string $message, Config $config)
22
    {
23
        $this->message = $message;
24
        $this->config = $config;
25
        $this->id = $this->uuid4();
26
    }
27
28
    public static function createFromException(Throwable $exception, Config $config): self
29
    {
30
        $payload = new Payload($exception->getMessage(), $config);
31
        $payload->pushException($exception);
32
        return $payload;
33
    }
34
35
    public function getMessage(): string
36
    {
37
        return $this->message;
38
    }
39
40
    public function newId(string $id): string
41
    {
42
        $old = $this->id;
43
        $this->id = $id;
44
        return $old;
45
    }
46
47
    public function getId(): string
48
    {
49
        return $this->id;
50
    }
51
52
    public function hasExceptions(): bool
53
    {
54
        return count($this->exceptions) > 0;
55
    }
56
57
    public function pushException(Throwable $exception): void
58
    {
59
        $trace = new Stacktrace($exception);
60
        $trace->setPath($this->config->getPath());
61
62
        $this->exceptions[] = [
63
            'exception' => \get_class($exception),
64
            'message' => $exception->getMessage(),
65
            'trace' => $trace->getFrames(),
66
        ];
67
68
        if ($previousException = $exception->getPrevious()) {
69
            $this->pushException($previousException);
70
        }
71
    }
72
73
    public function getExceptions(): array
74
    {
75
        return $this->exceptions;
76
    }
77
78
    public function setExtra(string $key, $value): void
79
    {
80
        $this->extra[$key] = $value;
81
    }
82
83
    public function setContext(array $context): void
84
    {
85
        $this->context = $context;
86
    }
87
88
    public function hasContext(): bool
89
    {
90
        return !empty($this->context);
91
    }
92
93
    public function getContext(): array
94
    {
95
        return $this->context;
96
    }
97
98
    /**
99
     * Get payload data
100
     *
101
     * @return array
102
     */
103
    public function getData(): array
104
    {
105
        $extra = array_merge($this->extra, [
106
            'id' => $this->getId(),
107
        ]);
108
109
        if ($this->config->hasTags()) {
110
            $extra['tags'] = $this->config->getTags();
111
        }
112
113
        if ($this->config->hasUser()) {
114
            $extra['user'] = $this->config->getUser();
115
        }
116
117
        if ($this->config->hasRelease()) {
118
            $extra['release'] = $this->config->getRelease();
119
        }
120
121
        $data = [
122
            'message' => $this->getMessage(),
123
            'extra' => $extra,
124
        ];
125
126
        if ($this->hasContext()) {
127
            $data['context'] = $this->getContext();
128
        }
129
130
        if ($this->hasExceptions()) {
131
            $data['exceptions'] = $this->getExceptions();
132
        }
133
134
        return $data;
135
    }
136
137
    /**
138
     * Get payload json encoded
139
     *
140
     * @return string
141
     */
142
    public function getEncodedData(): string
143
    {
144
        $data = $this->getData();
145
        $encoded = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
146
147
        if (JSON_ERROR_UTF8 === json_last_error()) {
148
            if (is_string($data)) {
0 ignored issues
show
introduced by
The condition is_string($data) is always false.
Loading history...
149
                $this->detectAndCleanUtf8($data);
150
            } elseif (is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
151
                array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
152
            }
153
            $encoded = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
154
        }
155
156
        if (JSON_ERROR_NONE !== json_last_error()) {
157
            $error = json_last_error_msg();
158
            throw new \LogicException(sprintf('Failed to encode json data: %s.', $error));
159
        }
160
161
        return $encoded;
162
    }
163
164
    /**
165
     * Detect invalid UTF-8 string characters and convert to valid UTF-8.
166
     * @see https://github.com/Seldaek/monolog/blob/master/src/Monolog/Formatter/NormalizerFormatter.php
167
     */
168
    public function detectAndCleanUtf8(&$data)
169
    {
170
        if (is_string($data) && !preg_match('//u', $data)) {
171
            $data = preg_replace_callback(
172
                '/[\x80-\xFF]+/',
173
                function ($m) {
174
                    return utf8_encode($m[0]);
175
                },
176
                $data
177
            );
178
            $data = str_replace(
179
                array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'),
180
                array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'),
181
                $data
182
            );
183
        }
184
    }
185
186
    /**
187
     * Get uuid v4
188
     *
189
     * @see http://www.php.net/manual/en/function.uniqid.php#94959
190
     * @return string
191
     */
192
    protected function uuid4(): string
193
    {
194
        mt_srand();
195
        return sprintf(
196
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
197
            // 32 bits for "time_low"
198
            mt_rand(0, 0xffff),
199
            mt_rand(0, 0xffff),
200
            // 16 bits for "time_mid"
201
            mt_rand(0, 0xffff),
202
            // 16 bits for "time_hi_and_version",
203
            // four most significant bits holds version number 4
204
            mt_rand(0, 0x0fff) | 0x4000,
205
            // 16 bits, 8 bits for "clk_seq_hi_res",
206
            // 8 bits for "clk_seq_low",
207
            // two most significant bits holds zero and one for variant DCE1.1
208
            mt_rand(0, 0x3fff) | 0x8000,
209
            // 48 bits for "node"
210
            mt_rand(0, 0xffff),
211
            mt_rand(0, 0xffff),
212
            mt_rand(0, 0xffff)
213
        );
214
    }
215
}
216