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

HipChatHandler::__construct()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 3
nop 10
dl 0
loc 16
rs 9.9
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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