Completed
Push — feature/improve-code ( e986a1...36787d )
by Avtandil
14:14
created

TelegramLog   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 254
Duplicated Lines 10.24 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 80.28%

Importance

Changes 7
Bugs 3 Features 1
Metric Value
wmc 30
c 7
b 3
f 1
lcom 2
cbo 4
dl 26
loc 254
rs 10
ccs 57
cts 71
cp 0.8028

12 Methods

Rating   Name   Duplication   Size   Complexity  
A isErrorLogActive() 0 4 1
A isDebugLogActive() 0 4 1
A isUpdateLogActive() 0 4 1
B initialize() 0 20 6
A initErrorLog() 13 13 3
A initDebugLog() 13 13 3
A getDebugLogTempStream() 0 11 3
A endDebugLogTempStream() 0 14 2
A initUpdateLog() 0 20 4
A error() 0 6 2
A debug() 0 6 2
A update() 0 6 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * This file is part of the TelegramBot package.
4
 *
5
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Longman\TelegramBot;
12
13
use Monolog\Logger;
14
use Monolog\Handler\StreamHandler;
15
use Monolog\Formatter\LineFormatter;
16
use Longman\TelegramBot\Exception\TelegramLogException;
17
18
class TelegramLog
19
{
20
    /**
21
     * Monolog instance
22
     *
23
     * @var \Monolog\Logger
24
     */
25
    static protected $monolog = null;
26
27
    /**
28
     * Monolog instance for update
29
     *
30
     * @var \Monolog\Logger
31
     */
32
    static protected $monolog_update = null;
33
34
    /**
35
     * Path for error log
36
     *
37
     * @var string
38
     */
39
    static protected $error_log_path = null;
40
41
    /**
42
     * Path for debug log
43
     *
44
     * @var string
45
     */
46
    static protected $debug_log_path = null;
47
48
    /**
49
     * Path for update log
50
     *
51
     * @var string
52
     */
53
    static protected $update_log_path = null;
54
55
    /**
56
     * Temporary stream handle for debug log
57
     *
58
     * @var null
59
     */
60
    static protected $debug_log_temp_stream_handle = null;
61
62
    /**
63
     * Initialize
64
     *
65
     * Initilize monolog instance. Singleton
66
     * Is possbile provide an external monolog instance
67
     *
68
     * @param \Monolog\Logger
69
     *
70
     * @return \Monolog\Logger
71
     */
72 3
    public static function initialize(Logger $external_monolog = null)
73
    {
74 3
        if (self::$monolog === null) {
75 3
            if ($external_monolog !== null) {
76 1
                self::$monolog = $external_monolog;
77
78 1
                foreach (self::$monolog->getHandlers() as $handler) {
79 1
                    if ($handler->getLevel() == 400) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Monolog\Handler\HandlerInterface as the method getLevel() does only exist in the following implementations of said interface: Monolog\Handler\AbstractHandler, Monolog\Handler\AbstractProcessingHandler, Monolog\Handler\AbstractSyslogHandler, Monolog\Handler\AmqpHandler, Monolog\Handler\BrowserConsoleHandler, Monolog\Handler\BufferHandler, Monolog\Handler\ChromePHPHandler, Monolog\Handler\CouchDBHandler, Monolog\Handler\CubeHandler, Monolog\Handler\DeduplicationHandler, Monolog\Handler\DoctrineCouchDBHandler, Monolog\Handler\DynamoDbHandler, Monolog\Handler\ElasticSearchHandler, Monolog\Handler\ErrorLogHandler, Monolog\Handler\ExceptionTestHandler, Monolog\Handler\FilterHandler, Monolog\Handler\FingersCrossedHandler, Monolog\Handler\FirePHPHandler, Monolog\Handler\FleepHookHandler, Monolog\Handler\FlowdockHandler, Monolog\Handler\GelfHandler, Monolog\Handler\GroupHandler, Monolog\Handler\HipChatHandler, Monolog\Handler\IFTTTHandler, Monolog\Handler\LogEntriesHandler, Monolog\Handler\LogglyHandler, Monolog\Handler\MailHandler, Monolog\Handler\MandrillHandler, Monolog\Handler\MongoDBHandler, Monolog\Handler\NativeMailerHandler, Monolog\Handler\NewRelicHandler, Monolog\Handler\NullHandler, Monolog\Handler\PHPConsoleHandler, Monolog\Handler\PsrHandler, Monolog\Handler\PushoverHandler, Monolog\Handler\RavenHandler, Monolog\Handler\RedisHandler, Monolog\Handler\RollbarHandler, Monolog\Handler\RotatingFileHandler, Monolog\Handler\SamplingHandler, Monolog\Handler\SlackHandler, Monolog\Handler\SocketHandler, Monolog\Handler\StreamHandler, Monolog\Handler\StubNewRelicHandler, Monolog\Handler\StubNewR...HandlerWithoutExtension, Monolog\Handler\SwiftMailerHandler, Monolog\Handler\SyslogHandler, Monolog\Handler\SyslogUdpHandler, Monolog\Handler\TestChromePHPHandler, Monolog\Handler\TestFirePHPHandler, Monolog\Handler\TestHandler, Monolog\Handler\WhatFailureGroupHandler, Monolog\Handler\ZendMonitorHandler.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
80 1
                        self::$error_log_path = true;
0 ignored issues
show
Documentation Bug introduced by
The property $error_log_path was declared of type string, but true is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
81
                    }
82 1
                    if ($handler->getLevel() == 100) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Monolog\Handler\HandlerInterface as the method getLevel() does only exist in the following implementations of said interface: Monolog\Handler\AbstractHandler, Monolog\Handler\AbstractProcessingHandler, Monolog\Handler\AbstractSyslogHandler, Monolog\Handler\AmqpHandler, Monolog\Handler\BrowserConsoleHandler, Monolog\Handler\BufferHandler, Monolog\Handler\ChromePHPHandler, Monolog\Handler\CouchDBHandler, Monolog\Handler\CubeHandler, Monolog\Handler\DeduplicationHandler, Monolog\Handler\DoctrineCouchDBHandler, Monolog\Handler\DynamoDbHandler, Monolog\Handler\ElasticSearchHandler, Monolog\Handler\ErrorLogHandler, Monolog\Handler\ExceptionTestHandler, Monolog\Handler\FilterHandler, Monolog\Handler\FingersCrossedHandler, Monolog\Handler\FirePHPHandler, Monolog\Handler\FleepHookHandler, Monolog\Handler\FlowdockHandler, Monolog\Handler\GelfHandler, Monolog\Handler\GroupHandler, Monolog\Handler\HipChatHandler, Monolog\Handler\IFTTTHandler, Monolog\Handler\LogEntriesHandler, Monolog\Handler\LogglyHandler, Monolog\Handler\MailHandler, Monolog\Handler\MandrillHandler, Monolog\Handler\MongoDBHandler, Monolog\Handler\NativeMailerHandler, Monolog\Handler\NewRelicHandler, Monolog\Handler\NullHandler, Monolog\Handler\PHPConsoleHandler, Monolog\Handler\PsrHandler, Monolog\Handler\PushoverHandler, Monolog\Handler\RavenHandler, Monolog\Handler\RedisHandler, Monolog\Handler\RollbarHandler, Monolog\Handler\RotatingFileHandler, Monolog\Handler\SamplingHandler, Monolog\Handler\SlackHandler, Monolog\Handler\SocketHandler, Monolog\Handler\StreamHandler, Monolog\Handler\StubNewRelicHandler, Monolog\Handler\StubNewR...HandlerWithoutExtension, Monolog\Handler\SwiftMailerHandler, Monolog\Handler\SyslogHandler, Monolog\Handler\SyslogUdpHandler, Monolog\Handler\TestChromePHPHandler, Monolog\Handler\TestFirePHPHandler, Monolog\Handler\TestHandler, Monolog\Handler\WhatFailureGroupHandler, Monolog\Handler\ZendMonitorHandler.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
83 1
                        self::$debug_log_path = true;
0 ignored issues
show
Documentation Bug introduced by
The property $debug_log_path was declared of type string, but true is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
84
                    }
85
                }
86
            } else {
87 2
                self::$monolog = new Logger('bot_log');
88
            }
89
        }
90 3
        return self::$monolog;
91
    }
92
93
    /**
94
     * Initialize error log
95
     *
96
     * @param string $path
97
     *
98
     * @return \Monolog\Logger
99
     * @throws \Longman\TelegramBot\Exception\TelegramLogException
100
     */
101 2 View Code Duplication
    public static function initErrorLog($path)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
102
    {
103 2
        if ($path === null || $path === '') {
104 1
            throw new TelegramLogException('Empty path for error log');
105
        }
106 1
        self::initialize();
107 1
        self::$error_log_path = $path;
108
109 1
        return self::$monolog->pushHandler(
110 1
            (new StreamHandler(self::$error_log_path, Logger::ERROR))
111 1
                ->setFormatter(new LineFormatter(null, null, true))
112
        );
113
    }
114
115
    /**
116
     * Initialize debug log
117
     *
118
     * @param string $path
119
     *
120
     * @return \Monolog\Logger
121
     * @throws \Longman\TelegramBot\Exception\TelegramLogException
122
     */
123 2 View Code Duplication
    public static function initDebugLog($path)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
124
    {
125 2
        if ($path === null || $path === '') {
126 1
            throw new TelegramLogException('Empty path for debug log');
127
        }
128 1
        self::initialize();
129 1
        self::$debug_log_path = $path;
130
131 1
        return self::$monolog->pushHandler(
132 1
            (new StreamHandler(self::$debug_log_path, Logger::DEBUG))
133 1
                ->setFormatter(new LineFormatter(null, null, true))
134
        );
135
    }
136
137
    /**
138
     * Get the stream handle of the temporary debug output
139
     *
140
     * @return mixed The stream if debug is active, else false
141
     */
142
    public static function getDebugLogTempStream()
143
    {
144
        if (self::$debug_log_temp_stream_handle === null) {
145
            if (self::isDebugLogActive()) {
146
                self::$debug_log_temp_stream_handle = fopen('php://temp', 'w+');
0 ignored issues
show
Documentation Bug introduced by
It seems like fopen('php://temp', 'w+') of type resource is incompatible with the declared type null of property $debug_log_temp_stream_handle.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
147
            } else {
148
                return false;
149
            }
150
        }
151
        return self::$debug_log_temp_stream_handle;
152
    }
153
154
    /**
155
     * Write the temporary debug stream to log and close the stream handle
156
     *
157
     * @param string $message Message (with placeholder) to write to the debug log
158
     */
159
    public static function endDebugLogTempStream($message = '%s')
160
    {
161
        if (self::$debug_log_temp_stream_handle !== null) {
162
            rewind(self::$debug_log_temp_stream_handle);
163
            self::debug(
164
                sprintf(
165
                    $message,
166
                    stream_get_contents(self::$debug_log_temp_stream_handle)
167
                )
168
            );
169
            fclose(self::$debug_log_temp_stream_handle);
170
            self::$debug_log_temp_stream_handle = null;
171
        }
172
    }
173
174
    /**
175
     * Initialize update log
176
     *
177
     * Initilize monolog instance. Singleton
178
     * Is possbile provide an external monolog instance
179
     *
180
     * @param string $path
181
     *
182
     * @return \Monolog\Logger
183
     * @throws \Longman\TelegramBot\Exception\TelegramLogException
184
     */
185 2
    public static function initUpdateLog($path)
186
    {
187 2
        if ($path === null || $path === '') {
188 1
            throw new TelegramLogException('Empty path for update log');
189
        }
190 1
        self::$update_log_path = $path;
191 1
        if (self::$monolog_update === null) {
192 1
            self::$monolog_update = new Logger('bot_update_log');
193
            // Create a formatter
194 1
            $output    = "%message%\n";
195 1
            $formatter = new LineFormatter($output);
196
197
            // Update handler
198 1
            $update_handler = new StreamHandler(self::$update_log_path, Logger::INFO);
199 1
            $update_handler->setFormatter($formatter);
200
201 1
            self::$monolog_update->pushHandler($update_handler);
202
        }
203 1
        return self::$monolog;
204
    }
205
206
    /**
207
     * Is error log active
208
     *
209
     * @return bool
210
     */
211 4
    public static function isErrorLogActive()
212
    {
213 4
        return (self::$error_log_path !== null);
214
    }
215
216
    /**
217
     * Is debug log active
218
     *
219
     * @return bool
220
     */
221 2
    public static function isDebugLogActive()
222
    {
223 2
        return (self::$debug_log_path !== null);
224
    }
225
226
    /**
227
     * Is update log active
228
     *
229
     * @return bool
230
     */
231 1
    public static function isUpdateLogActive()
232
    {
233 1
        return (self::$update_log_path !== null);
234
    }
235
236
    /**
237
     * Report error log
238
     *
239
     * @param string $text
240
     */
241 4
    public static function error($text)
242
    {
243 4
        if (self::isErrorLogActive()) {
244 4
            self::$monolog->error($text);
245
        }
246 4
    }
247
248
    /**
249
     * Report debug log
250
     *
251
     * @param string $text
252
     */
253 2
    public static function debug($text)
254
    {
255 2
        if (self::isDebugLogActive()) {
256 2
            self::$monolog->debug($text);
257
        }
258 2
    }
259
260
    /**
261
     * Report update log
262
     *
263
     * @param string $text
264
     */
265 1
    public static function update($text)
266
    {
267 1
        if (self::isUpdateLogActive()) {
268 1
            self::$monolog_update->info($text);
269
        }
270 1
    }
271
}
272