Passed
Push — master ( 6a76e0...a126cd )
by Sergey
06:56
created

SlackTarget::formatMessageAttachment()   F

Complexity

Conditions 18
Paths 1344

Size

Total Lines 90
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 90
rs 2
cc 18
eloc 63
nc 1344
nop 1

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
 * Slack log target for Yii 2
4
 *
5
 * @see       https://github.com/sergeymakinen/yii2-slack-log
6
 * @copyright Copyright (c) 2016 Sergey Makinen (https://makinen.ru)
7
 * @license   https://github.com/sergeymakinen/yii2-slack-log/blob/master/LICENSE The MIT License
8
 */
9
10
namespace sergeymakinen\log;
11
12
use yii\helpers\Json;
13
use yii\helpers\Url;
14
use yii\helpers\VarDumper;
15
use yii\log\Logger;
16
use yii\log\Target;
17
use yii\web\Request;
18
19
class SlackTarget extends Target
20
{
21
    /**
22
     * Incoming Webhook URL.
23
     *
24
     * @var string
25
     */
26
    public $webhookUrl;
27
28
    /**
29
     * Displayed username.
30
     *
31
     * @var string
32
     */
33
    public $username;
34
35
    /**
36
     * Icon URL.
37
     *
38
     * @var string
39
     */
40
    public $iconUrl;
41
42
    /**
43
     * Icon Emoji.
44
     *
45
     * @var string
46
     */
47
    public $iconEmoji;
48
49
    /**
50
     * Channel or Direct Message.
51
     *
52
     * @var string
53
     */
54
    public $channel;
55
56
    /**
57
     * Colors per a Logger level.
58
     *
59
     * @var array
60
     */
61
    public $colors = [
62
        Logger::LEVEL_ERROR => 'danger',
63
        Logger::LEVEL_WARNING => 'warning'
64
    ];
65
66
    /**
67
     * @inheritDoc
68
     */
69
    public function export()
70
    {
71
        if (!isset($this->webhookUrl)) {
72
            return;
73
        }
74
75
        $payload = [
76
            'parse' => 'none',
77
            'attachments' => array_map([$this, 'formatMessageAttachment'], $this->messages)
78
        ];
79
        if (isset($this->username)) {
80
            $payload['username'] = $this->username;
81
        }
82
        if (isset($this->iconUrl)) {
83
            $payload['icon_url'] = $this->iconUrl;
84
        }
85
        if (isset($this->iconEmoji)) {
86
            $payload['icon_emoji'] = $this->iconEmoji;
87
        }
88
        if (isset($this->channel)) {
89
            $payload['channel'] = $this->channel;
90
        }
91
        if (extension_loaded('curl')) {
92
            $curl = curl_init();
93
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
94
            curl_setopt($curl, CURLOPT_POST, true);
95
            curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json; charset=UTF-8']);
96
            curl_setopt($curl, CURLOPT_POSTFIELDS, Json::encode($payload));
97
            curl_setopt($curl, CURLOPT_URL, $this->webhookUrl);
98
            curl_exec($curl);
99
            curl_close($curl);
100
        } else {
101
            $context = stream_context_create([
102
                'http' => [
103
                    'method' => 'POST',
104
                    'header' => "Content-Type: application/json; charset=UTF-8\r\n",
105
                    'content' => Json::encode($payload)
106
                ]
107
            ]);
108
            @file_get_contents($this->webhookUrl, false, $context);
109
        }
110
    }
111
112
    /**
113
     * Encodes special chars in a message as HTML entities.
114
     *
115
     * @param string $message
116
     *
117
     * @return string
118
     */
119
    protected static function encodeMessage($message)
120
    {
121
        return htmlspecialchars($message, ENT_NOQUOTES, 'UTF-8');
122
    }
123
124
    /**
125
     * Returns a properly formatted message attachment for Slack API.
126
     *
127
     * @param array $message
128
     *
129
     * @return array
130
     */
131
    protected function formatMessageAttachment($message)
0 ignored issues
show
Coding Style introduced by
formatMessageAttachment uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
132
    {
133
        list($text, $level, $category, $timestamp) = $message;
134
        $attachment = [
135
            'fallback' => static::encodeMessage($this->formatMessage($message)),
136
            'title' => ucwords(Logger::getLevelName($level)),
137
            'fields' => [
138
                [
139
                    'title' => 'Level',
140
                    'value' => Logger::getLevelName($level),
141
                    'short' => true
142
                ],
143
                [
144
                    'title' => 'Category',
145
                    'value' => '`' . static::encodeMessage($category) . '`',
146
                    'short' => true
147
                ]
148
            ],
149
            'footer' => static::class,
150
            'ts' => (integer) round($timestamp),
151
            'mrkdwn_in' => [
152
                'fields',
153
                'text'
154
            ]
155
        ];
156
        if ($this->prefix !== null) {
157
            $attachment['fields'][] = [
158
                'title' => 'Prefix',
159
                'value' => '`' . static::encodeMessage(call_user_func($this->prefix, $message)) . '`',
160
                'short' => true,
161
            ];
162
        }
163
        if (isset(\Yii::$app)) {
164
            if (isset($_SERVER['argv'])) {
165
                $attachment['author_name'] = implode(' ', $_SERVER['argv']);
166
            } elseif (\Yii::$app->getRequest() instanceof Request) {
167
                $attachment['author_name'] = Url::current([], true);
1 ignored issue
show
Bug introduced by
The method current() does not seem to exist on object<yii\helpers\Url>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
168
                $attachment['author_link'] = $attachment['author_name'];
169
                $attachment['fields'][] = [
170
                    'title' => 'User IP',
171
                    'value' => \Yii::$app->getRequest()->getUserIP(),
172
                    'short' => true,
173
                ];
174
            }
175
            if (\Yii::$app->has('user', true) && !is_null(\Yii::$app->getUser())) {
176
                $user = \Yii::$app->getUser()->getIdentity(false);
1 ignored issue
show
Bug introduced by
The method getUser does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
177
                if (isset($user)) {
178
                    $attachment['fields'][] = [
179
                        'title' => 'User ID',
180
                        'value' => $user->getId(),
181
                        'short' => true,
182
                    ];
183
                }
184
            }
185
            if (\Yii::$app->has('session', true) && !is_null(\Yii::$app->getSession())) {
186
                if (\Yii::$app->getSession()->getIsActive()) {
187
                    $attachment['fields'][] = [
188
                        'title' => 'Session ID',
189
                        'value' => \Yii::$app->getSession()->getId(),
1 ignored issue
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
190
                        'short' => true,
191
                    ];
192
                }
193
            }
194
        }
195
        if (isset($this->colors[$level])) {
196
            $attachment['color'] = $this->colors[$level];
197
        }
198
        if (!is_string($text)) {
199
            if ($text instanceof \Throwable || $text instanceof \Exception) {
200
                $text = (string) $text;
201
            } else {
202
                $text = VarDumper::export($text);
1 ignored issue
show
Bug introduced by
The method export() does not seem to exist on object<yii\helpers\VarDumper>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
203
            }
204
        }
205
        $attachment['text'] = "```\n" . static::encodeMessage($text) . "\n```";
206
        $traces = [];
207
        if (isset($message[4])) {
208
            foreach ($message[4] as $trace) {
209
                $traces[] = "in {$trace['file']}:{$trace['line']}";
210
            }
211
        }
212
        if (!empty($traces)) {
213
            $attachment['fields'][] = [
214
                'title' => 'Stack Trace',
215
                'value' => "```\n" . static::encodeMessage(implode("\n", $traces)) . "\n```",
216
                'short' => false,
217
            ];
218
        }
219
        return $attachment;
220
    }
221
}
222