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

SlackRecord::getSlackData()   C

Complexity

Conditions 13
Paths 120

Size

Total Lines 72
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 44
nc 120
nop 1
dl 0
loc 72
rs 6.45
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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\Slack;
13
14
use Monolog\Logger;
15
use Monolog\Formatter\NormalizerFormatter;
16
use Monolog\Formatter\FormatterInterface;
17
18
/**
19
 * Slack record utility helping to log to Slack webhooks or API.
20
 *
21
 * @author Greg Kedzierski <[email protected]>
22
 * @author Haralan Dobrev <[email protected]>
23
 * @see    https://api.slack.com/incoming-webhooks
24
 * @see    https://api.slack.com/docs/message-attachments
25
 */
26
class SlackRecord
27
{
28
    const COLOR_DANGER = 'danger';
29
30
    const COLOR_WARNING = 'warning';
31
32
    const COLOR_GOOD = 'good';
33
34
    const COLOR_DEFAULT = '#e3e4e6';
35
36
    /**
37
     * Slack channel (encoded ID or name)
38
     * @var string|null
39
     */
40
    private $channel;
41
42
    /**
43
     * Name of a bot
44
     * @var string|null
45
     */
46
    private $username;
47
48
    /**
49
     * User icon e.g. 'ghost', 'http://example.com/user.png'
50
     * @var string
51
     */
52
    private $userIcon;
53
54
    /**
55
     * Whether the message should be added to Slack as attachment (plain text otherwise)
56
     * @var bool
57
     */
58
    private $useAttachment;
59
60
    /**
61
     * Whether the the context/extra messages added to Slack as attachments are in a short style
62
     * @var bool
63
     */
64
    private $useShortAttachment;
65
66
    /**
67
     * Whether the attachment should include context and extra data
68
     * @var bool
69
     */
70
    private $includeContextAndExtra;
71
72
    /**
73
     * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']
74
     * @var array
75
     */
76
    private $excludeFields;
77
78
    /**
79
     * @var FormatterInterface
80
     */
81
    private $formatter;
82
83
    /**
84
     * @var NormalizerFormatter
85
     */
86
    private $normalizerFormatter;
87
88
    public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null)
89
    {
90
        $this->channel = $channel;
91
        $this->username = $username;
92
        $this->userIcon = trim($userIcon, ':');
93
        $this->useAttachment = $useAttachment;
94
        $this->useShortAttachment = $useShortAttachment;
95
        $this->includeContextAndExtra = $includeContextAndExtra;
96
        $this->excludeFields = $excludeFields;
97
        $this->formatter = $formatter;
98
99
        if ($this->includeContextAndExtra) {
100
            $this->normalizerFormatter = new NormalizerFormatter();
101
        }
102
    }
103
104
    public function getSlackData(array $record)
105
    {
106
        $dataArray = array();
107
        $record = $this->excludeFields($record);
108
109
        if ($this->username) {
110
            $dataArray['username'] = $this->username;
111
        }
112
113
        if ($this->channel) {
114
            $dataArray['channel'] = $this->channel;
115
        }
116
117
        if ($this->formatter && !$this->useAttachment) {
118
            $message = $this->formatter->format($record);
119
        } else {
120
            $message = $record['message'];
121
        }
122
123
        if ($this->useAttachment) {
124
            $attachment = array(
125
                'fallback'  => $message,
126
                'text'      => $message,
127
                'color'     => $this->getAttachmentColor($record['level']),
128
                'fields'    => array(),
129
                'mrkdwn_in' => array('fields'),
130
                'ts'        => $record['datetime']->getTimestamp()
131
            );
132
133
            if ($this->useShortAttachment) {
134
                $attachment['title'] = $record['level_name'];
135
            } else {
136
                $attachment['title'] = 'Message';
137
                $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']);
138
            }
139
140
141
            if ($this->includeContextAndExtra) {
142
                foreach (array('extra', 'context') as $key) {
143
                    if (empty($record[$key])) {
144
                        continue;
145
                    }
146
147
                    if ($this->useShortAttachment) {
148
                        $attachment['fields'][] = $this->generateAttachmentField(
149
                            ucfirst($key),
150
                            $record[$key]
151
                        );
152
                    } else {
153
                        // Add all extra fields as individual fields in attachment
154
                        $attachment['fields'] = array_merge(
155
                            $attachment['fields'],
156
                            $this->generateAttachmentFields($record[$key])
157
                        );
158
                    }
159
                }
160
            }
161
162
            $dataArray['attachments'] = array($attachment);
163
        } else {
164
            $dataArray['text'] = $message;
165
        }
166
167
        if ($this->userIcon) {
168
            if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) {
169
                $dataArray['icon_url'] = $this->userIcon;
170
            } else {
171
                $dataArray['icon_emoji'] = ":{$this->userIcon}:";
172
            }
173
        }
174
175
        return $dataArray;
176
    }
177
178
    /**
179
     * Returned a Slack message attachment color associated with
180
     * provided level.
181
     *
182
     * @param  int    $level
183
     * @return string
184
     */
185
    public function getAttachmentColor($level)
186
    {
187
        switch (true) {
188
            case $level >= Logger::ERROR:
189
                return self::COLOR_DANGER;
190
            case $level >= Logger::WARNING:
191
                return self::COLOR_WARNING;
192
            case $level >= Logger::INFO:
193
                return self::COLOR_GOOD;
194
            default:
195
                return self::COLOR_DEFAULT;
196
        }
197
    }
198
199
    /**
200
     * Stringifies an array of key/value pairs to be used in attachment fields
201
     *
202
     * @param array $fields
203
     *
204
     * @return string
205
     */
206
    public function stringify($fields)
207
    {
208
        $normalized = $this->normalizerFormatter->format($fields);
209
        $prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128;
210
211
        $hasSecondDimension = count(array_filter($normalized, 'is_array'));
212
        $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric'));
213
214
        return $hasSecondDimension || $hasNonNumericKeys
215
            ? json_encode($normalized, $prettyPrintFlag)
216
            : json_encode($normalized);
217
    }
218
219
    /**
220
     * Sets the formatter
221
     *
222
     * @param FormatterInterface $formatter
223
     */
224
    public function setFormatter(FormatterInterface $formatter)
225
    {
226
        $this->formatter = $formatter;
227
    }
228
229
    /**
230
     * Generates attachment field
231
     *
232
     * @param string $title
233
     * @param string|array $value\
234
     *
235
     * @return array
236
     */
237
    private function generateAttachmentField($title, $value)
238
    {
239
        $value = is_array($value)
240
            ? sprintf('```%s```', $this->stringify($value))
241
            : $value;
242
243
        return array(
244
            'title' => $title,
245
            'value' => $value,
246
            'short' => false
247
        );
248
    }
249
250
    /**
251
     * Generates a collection of attachment fields from array
252
     *
253
     * @param array $data
254
     *
255
     * @return array
256
     */
257
    private function generateAttachmentFields(array $data)
258
    {
259
        $fields = array();
260
        foreach ($data as $key => $value) {
261
            $fields[] = $this->generateAttachmentField($key, $value);
262
        }
263
264
        return $fields;
265
    }
266
267
    /**
268
     * Get a copy of record with fields excluded according to $this->excludeFields
269
     *
270
     * @param array $record
271
     *
272
     * @return array
273
     */
274
    private function excludeFields(array $record)
275
    {
276
        foreach ($this->excludeFields as $field) {
277
            $keys = explode('.', $field);
278
            $node = &$record;
279
            $lastKey = end($keys);
280
            foreach ($keys as $key) {
281
                if (!isset($node[$key])) {
282
                    break;
283
                }
284
                if ($lastKey === $key) {
285
                    unset($node[$key]);
286
                    break;
287
                }
288
                $node = &$node[$key];
289
            }
290
        }
291
292
        return $record;
293
    }
294
}
295