Completed
Push — master ( cdeef2...f03c61 )
by Justin
03:14
created

HipChatHandler::combineRecords()   B

Complexity

Conditions 7
Paths 36

Size

Total Lines 66
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 42
nc 36
nop 1
dl 0
loc 66
rs 8.3146
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Monolog package.
5
 *
6
 * (c) Jordi Boggiano <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Monolog\Handler;
13
14
use Monolog\Logger;
15
16
/**
17
 * Sends notifications through the hipchat api to a hipchat room
18
 *
19
 * Notes:
20
 * API token - HipChat API token
21
 * Room      - HipChat Room Id or name, where messages are sent
22
 * Name      - Name used to send the message (from)
23
 * notify    - Should the message trigger a notification in the clients
24
 * version   - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2)
25
 *
26
 * @author Rafael Dohms <[email protected]>
27
 * @see    https://www.hipchat.com/docs/api
28
 */
29
class HipChatHandler extends SocketHandler
30
{
31
    /**
32
     * Use API version 1
33
     */
34
    const API_V1 = 'v1';
35
36
    /**
37
     * Use API version v2
38
     */
39
    const API_V2 = 'v2';
40
41
    /**
42
     * The maximum allowed length for the name used in the "from" field.
43
     */
44
    const MAXIMUM_NAME_LENGTH = 15;
45
46
    /**
47
     * The maximum allowed length for the message.
48
     */
49
    const MAXIMUM_MESSAGE_LENGTH = 9500;
50
51
    /**
52
     * @var string
53
     */
54
    private $token;
55
56
    /**
57
     * @var string
58
     */
59
    private $room;
60
61
    /**
62
     * @var string
63
     */
64
    private $name;
65
66
    /**
67
     * @var bool
68
     */
69
    private $notify;
70
71
    /**
72
     * @var string
73
     */
74
    private $format;
75
76
    /**
77
     * @var string
78
     */
79
    private $host;
80
81
    /**
82
     * @var string
83
     */
84
    private $version;
85
86
    /**
87
     * @param string $token   HipChat API Token
88
     * @param string $room    The room that should be alerted of the message (Id or Name)
89
     * @param string $name    Name used in the "from" field.
90
     * @param bool   $notify  Trigger a notification in clients or not
91
     * @param int    $level   The minimum logging level at which this handler will be triggered
92
     * @param bool   $bubble  Whether the messages that are handled can bubble up the stack or not
93
     * @param bool   $useSSL  Whether to connect via SSL.
94
     * @param string $format  The format of the messages (default to text, can be set to html if you have html in the messages)
95
     * @param string $host    The HipChat server hostname.
96
     * @param string $version The HipChat API version (default HipChatHandler::API_V1)
97
     */
98
    public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1)
99
    {
100
        if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) {
101
            throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.');
102
        }
103
104
        $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80';
105
        parent::__construct($connectionString, $level, $bubble);
106
107
        $this->token = $token;
108
        $this->name = $name;
109
        $this->notify = $notify;
110
        $this->room = $room;
111
        $this->format = $format;
112
        $this->host = $host;
113
        $this->version = $version;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     *
119
     * @param  array  $record
120
     * @return string
121
     */
122
    protected function generateDataStream($record)
123
    {
124
        $content = $this->buildContent($record);
125
126
        return $this->buildHeader($content) . $content;
127
    }
128
129
    /**
130
     * Builds the body of API call
131
     *
132
     * @param  array  $record
133
     * @return string
134
     */
135
    private function buildContent($record)
136
    {
137
        $dataArray = array(
138
            'notify' => $this->version == self::API_V1 ?
139
                ($this->notify ? 1 : 0) :
140
                ($this->notify ? 'true' : 'false'),
141
            'message' => $record['formatted'],
142
            'message_format' => $this->format,
143
            'color' => $this->getAlertColor($record['level']),
144
        );
145
146
        if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) {
147
            if (function_exists('mb_substr')) {
148
                $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
149
            } else {
150
                $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
151
            }
152
        }
153
154
        // if we are using the legacy API then we need to send some additional information
155
        if ($this->version == self::API_V1) {
156
            $dataArray['room_id'] = $this->room;
157
        }
158
159
        // append the sender name if it is set
160
        // always append it if we use the v1 api (it is required in v1)
161
        if ($this->version == self::API_V1 || $this->name !== null) {
162
            $dataArray['from'] = (string) $this->name;
163
        }
164
165
        return http_build_query($dataArray);
166
    }
167
168
    /**
169
     * Builds the header of the API Call
170
     *
171
     * @param  string $content
172
     * @return string
173
     */
174
    private function buildHeader($content)
175
    {
176
        if ($this->version == self::API_V1) {
177
            $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n";
178
        } else {
179
            // needed for rooms with special (spaces, etc) characters in the name
180
            $room = rawurlencode($this->room);
181
            $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n";
182
        }
183
184
        $header .= "Host: {$this->host}\r\n";
185
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
186
        $header .= "Content-Length: " . strlen($content) . "\r\n";
187
        $header .= "\r\n";
188
189
        return $header;
190
    }
191
192
    /**
193
     * Assigns a color to each level of log records.
194
     *
195
     * @param  int    $level
196
     * @return string
197
     */
198
    protected function getAlertColor($level)
199
    {
200
        switch (true) {
201
            case $level >= Logger::ERROR:
202
                return 'red';
203
            case $level >= Logger::WARNING:
204
                return 'yellow';
205
            case $level >= Logger::INFO:
206
                return 'green';
207
            case $level == Logger::DEBUG:
208
                return 'gray';
209
            default:
210
                return 'yellow';
211
        }
212
    }
213
214
    /**
215
     * {@inheritdoc}
216
     *
217
     * @param array $record
218
     */
219
    protected function write(array $record)
220
    {
221
        parent::write($record);
222
        $this->closeSocket();
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function handleBatch(array $records)
229
    {
230
        if (count($records) == 0) {
231
            return true;
232
        }
233
234
        $batchRecords = $this->combineRecords($records);
235
236
        $handled = false;
237
        foreach ($batchRecords as $batchRecord) {
238
            if ($this->isHandling($batchRecord)) {
239
                $this->write($batchRecord);
240
                $handled = true;
241
            }
242
        }
243
244
        if (!$handled) {
245
            return false;
246
        }
247
248
        return false === $this->bubble;
249
    }
250
251
    /**
252
     * Combines multiple records into one. Error level of the combined record
253
     * will be the highest level from the given records. Datetime will be taken
254
     * from the first record.
255
     *
256
     * @param $records
257
     * @return array
258
     */
259
    private function combineRecords($records)
260
    {
261
        $batchRecord = null;
262
        $batchRecords = array();
263
        $messages = array();
264
        $formattedMessages = array();
265
        $level = 0;
266
        $levelName = null;
267
        $datetime = null;
268
269
        foreach ($records as $record) {
270
            $record = $this->processRecord($record);
271
272
            if ($record['level'] > $level) {
273
                $level = $record['level'];
274
                $levelName = $record['level_name'];
275
            }
276
277
            if (null === $datetime) {
278
                $datetime = $record['datetime'];
279
            }
280
281
            $messages[] = $record['message'];
282
            $messageStr = implode(PHP_EOL, $messages);
283
            $formattedMessages[] = $this->getFormatter()->format($record);
284
            $formattedMessageStr = implode('', $formattedMessages);
285
286
            $batchRecord = array(
287
                'message'   => $messageStr,
288
                'formatted' => $formattedMessageStr,
289
                'context'   => array(),
290
                'extra'     => array(),
291
            );
292
293
            if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) {
294
                // Pop the last message and implode the remaining messages
295
                $lastMessage = array_pop($messages);
296
                $lastFormattedMessage = array_pop($formattedMessages);
297
                $batchRecord['message'] = implode(PHP_EOL, $messages);
298
                $batchRecord['formatted'] = implode('', $formattedMessages);
299
300
                $batchRecords[] = $batchRecord;
301
                $messages = array($lastMessage);
302
                $formattedMessages = array($lastFormattedMessage);
303
304
                $batchRecord = null;
305
            }
306
        }
307
308
        if (null !== $batchRecord) {
309
            $batchRecords[] = $batchRecord;
310
        }
311
312
        // Set the max level and datetime for all records
313
        foreach ($batchRecords as &$batchRecord) {
314
            $batchRecord = array_merge(
315
                $batchRecord,
316
                array(
317
                    'level'      => $level,
318
                    'level_name' => $levelName,
319
                    'datetime'   => $datetime,
320
                )
321
            );
322
        }
323
324
        return $batchRecords;
325
    }
326
327
    /**
328
     * Validates the length of a string.
329
     *
330
     * If the `mb_strlen()` function is available, it will use that, as HipChat
331
     * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`.
332
     *
333
     * Note that this might cause false failures in the specific case of using
334
     * a valid name with less than 16 characters, but 16 or more bytes, on a
335
     * system where `mb_strlen()` is unavailable.
336
     *
337
     * @param string $str
338
     * @param int    $length
339
     *
340
     * @return bool
341
     */
342
    private function validateStringLength($str, $length)
343
    {
344
        if (function_exists('mb_strlen')) {
345
            return (mb_strlen($str) <= $length);
346
        }
347
348
        return (strlen($str) <= $length);
349
    }
350
}
351