Passed
Push — feature/rebusify ( 495106...67b08f )
by Paul
06:10 queued 02:03
created

Console   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 428
Duplicated Lines 0 %

Test Coverage

Coverage 42.59%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 137
c 6
b 0
f 0
dl 0
loc 428
ccs 69
cts 162
cp 0.4259
rs 5.04
wmc 57

32 Methods

Rating   Name   Duplication   Size   Complexity  
A error() 0 3 1
A critical() 0 3 1
A debug() 0 3 1
A emergency() 0 3 1
A clear() 0 4 1
A __toString() 0 3 1
A get() 0 5 2
A alert() 0 3 1
A humanLevel() 0 4 1
A humanSize() 0 8 3
A canLogEntry() 0 7 3
A warning() 0 3 1
A getLevel() 0 3 1
A size() 0 5 2
A normalizeValue() 0 8 3
A log() 0 16 3
A notice() 0 3 1
A getLevels() 0 4 1
A info() 0 3 1
A getBacktraceLineFromData() 0 6 2
A normalizeThrowableMessage() 0 6 2
A reset() 0 11 2
A __construct() 0 7 2
A buildBacktraceLine() 0 5 1
A interpolate() 0 10 4
A once() 0 17 3
A logOnce() 0 15 3
A getBacktraceLine() 0 11 3
A getMessageFromData() 0 5 2
A normalizeBacktraceLine() 0 11 1
A buildLogEntry() 0 7 1
A isObjectOrArray() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like Console often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Console, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use DateTime;
6
use GeminiLabs\SiteReviews\Helper;
7
use ReflectionClass;
8
use Throwable;
9
10
class Console
11
{
12
    const DEBUG = 0;      // Detailed debug information
13
    const INFO = 1;       // Interesting events
14
    const NOTICE = 2;     // Normal but significant events
15
    const WARNING = 4;    // Exceptional occurrences that are not errors
16
    const ERROR = 8;      // Runtime errors that do not require immediate action
17
    const CRITICAL = 16;  // Critical conditions
18
    const ALERT = 32;     // Action must be taken immediately
19
    const EMERGENCY = 64; // System is unusable
20
21
    protected $file;
22
    protected $log;
23
    protected $logOnceKey = 'glsr_log_once';
24
25 1
    public function __construct()
26
    {
27 1
        $this->file = glsr()->path('console.log');
28 1
        $this->log = file_exists($this->file)
29
            ? file_get_contents($this->file)
30 1
            : '';
31 1
        $this->reset();
32 1
    }
33
34
    /**
35
     * @return string
36
     */
37
    public function __toString()
38
    {
39
        return $this->get();
40
    }
41
42
    /**
43
     * Action must be taken immediately
44
     * Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
45
     * @param mixed $message
46
     * @param array $context
47
     * @return static
48
     */
49
    public function alert($message, array $context = [])
50
    {
51
        return $this->log(static::ALERT, $message, $context);
52
    }
53
54
    /**
55
     * @return void
56
     */
57
    public function clear()
58
    {
59
        $this->log = '';
60
        file_put_contents($this->file, $this->log);
61
    }
62
63
    /**
64
     * Critical conditions
65
     * Example: Application component unavailable, unexpected exception.
66
     * @param mixed $message
67
     * @param array $context
68
     * @return static
69
     */
70
    public function critical($message, array $context = [])
71
    {
72
        return $this->log(static::CRITICAL, $message, $context);
73
    }
74
75
    /**
76
     * Detailed debug information.
77
     * @param mixed $message
78
     * @param array $context
79
     * @return static
80
     */
81
    public function debug($message, array $context = [])
82
    {
83
        return $this->log(static::DEBUG, $message, $context);
84
    }
85
86
    /**
87
     * System is unusable.
88
     * @param mixed $message
89
     * @param array $context
90
     * @return static
91
     */
92
    public function emergency($message, array $context = [])
93
    {
94
        return $this->log(static::EMERGENCY, $message, $context);
95
    }
96
97
    /**
98
     * Runtime errors that do not require immediate action but should typically be logged and monitored.
99
     * @param mixed $message
100
     * @param array $context
101
     * @return static
102
     */
103
    public function error($message, array $context = [])
104
    {
105
        return $this->log(static::ERROR, $message, $context);
106
    }
107
108
    /**
109
     * @return string
110
     */
111
    public function get()
112
    {
113
        return empty($this->log)
114
            ? __('Console is empty', 'site-reviews')
115
            : $this->log;
116
    }
117
118
    /**
119
     * @return int
120
     */
121 1
    public function getLevel()
122
    {
123 1
        return intval(apply_filters('site-reviews/console/level', static::INFO));
124
    }
125
126
    /**
127
     * @return array
128
     */
129 1
    public function getLevels()
130
    {
131 1
        $constants = (new ReflectionClass(__CLASS__))->getConstants();
132 1
        return array_map('strtolower', array_flip($constants));
133
    }
134
135
    /**
136
     * @return string
137
     */
138
    public function humanLevel()
139
    {
140
        $level = $this->getLevel();
141
        return sprintf('%s (%d)', strtoupper(glsr_get($this->getLevels(), $level, 'unknown')), $level);
142
    }
143
144
    /**
145
     * @param string|null $valueIfEmpty
146
     * @return string
147
     */
148
    public function humanSize($valueIfEmpty = null)
149
    {
150
        $bytes = $this->size();
151
        if (empty($bytes) && is_string($valueIfEmpty)) {
152
            return $valueIfEmpty;
153
        }
154
        $exponent = floor(log(max($bytes, 1), 1024));
155
        return round($bytes / pow(1024, $exponent), 2).' '.['bytes', 'KB', 'MB', 'GB'][$exponent];
156
    }
157
158
    /**
159
     * Interesting events
160
     * Example: User logs in, SQL logs.
161
     * @param mixed $message
162
     * @param array $context
163
     * @return static
164
     */
165
    public function info($message, array $context = [])
166
    {
167
        return $this->log(static::INFO, $message, $context);
168
    }
169
170
    /**
171
     * @param int $level
172
     * @param mixed $message
173
     * @param array $context
174
     * @param string $backtraceLine
175
     * @return static
176
     */
177 1
    public function log($level, $message, $context = [], $backtraceLine = '')
178
    {
179 1
        if (empty($backtraceLine)) {
180 1
            $backtraceLine = $this->getBacktraceLine();
181
        }
182 1
        if ($this->canLogEntry($level, $backtraceLine)) {
183 1
            $levelName = glsr_get($this->getLevels(), $level);
184 1
            $context = glsr(Helper::class)->consolidateArray($context);
185 1
            $backtraceLine = $this->normalizeBacktraceLine($backtraceLine);
186 1
            $message = $this->interpolate($message, $context);
187 1
            $entry = $this->buildLogEntry($levelName, $message, $backtraceLine);
188 1
            file_put_contents($this->file, $entry.PHP_EOL, FILE_APPEND | LOCK_EX);
189 1
            apply_filters('console', $message, $levelName, $backtraceLine); // Show in Blackbar plugin if installed
190 1
            $this->reset();
191
        }
192 1
        return $this;
193
    }
194
195
    /**
196
     * @return void
197
     */
198
    public function logOnce()
199
    {
200
        $once = glsr(Helper::class)->consolidateArray(glsr()->{$this->logOnceKey});
201
        $levels = $this->getLevels();
202
        foreach ($once as $entry) {
203
            $levelName = glsr_get($entry, 'level');
204
            if (!in_array($levelName, $levels)) {
205
                continue;
206
            }
207
            $level = glsr_get(array_flip($levels), $levelName);
208
            $message = glsr_get($entry, 'message');
209
            $backtraceLine = glsr_get($entry, 'backtrace');
210
            $this->log($level, $message, [], $backtraceLine);
211
        }
212
        glsr()->{$this->logOnceKey} = [];
213
    }
214
215
    /**
216
     * Normal but significant events.
217
     * @param mixed $message
218
     * @param array $context
219
     * @return static
220
     */
221 1
    public function notice($message, array $context = [])
222
    {
223 1
        return $this->log(static::NOTICE, $message, $context);
224
    }
225
226
    /**
227
     * @param string $levelName
228
     * @param string $handle
229
     * @param mixed $data
230
     * @return void
231
     */
232
    public function once($levelName, $handle, $data)
233
    {
234
        $once = glsr(Helper::class)->consolidateArray(glsr()->{$this->logOnceKey});
235
        $filtered = array_filter($once, function ($entry) use ($levelName, $handle) {
236
            return glsr_get($entry, 'level') == $levelName
237
                && glsr_get($entry, 'handle') == $handle;
238
        });
239
        if (!empty($filtered)) {
240
            return;
241
        }
242
        $once[] = [
243
            'backtrace' => $this->getBacktraceLineFromData($data),
244
            'handle' => $handle,
245
            'level' => $levelName,
246
            'message' => '[RECURRING] '.$this->getMessageFromData($data),
247
        ];
248
        glsr()->{$this->logOnceKey} = $once;
249
    }
250
251
    /**
252
     * @return int
253
     */
254 1
    public function size()
255
    {
256 1
        return file_exists($this->file)
257 1
            ? filesize($this->file)
258 1
            : 0;
259
    }
260
261
    /**
262
     * Exceptional occurrences that are not errors
263
     * Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
264
     * @param mixed $message
265
     * @param array $context
266
     * @return static
267
     */
268
    public function warning($message, array $context = [])
269
    {
270
        return $this->log(static::WARNING, $message, $context);
271
    }
272
273
    /**
274
     * @param array $backtrace
275
     * @param int $index
276
     * @return string
277
     */
278 1
    protected function buildBacktraceLine($backtrace, $index)
279
    {
280 1
        return sprintf('%s:%s',
281 1
            glsr_get($backtrace, $index.'.file'), // realpath
282 1
            glsr_get($backtrace, $index.'.line')
283
        );
284
    }
285
286
    /**
287
     * @param string $levelName
288
     * @param mixed $message
289
     * @param string $backtraceLine
290
     * @return string
291
     */
292 1
    protected function buildLogEntry($levelName, $message, $backtraceLine = '')
293
    {
294 1
        return sprintf('[%s] %s [%s] %s',
295 1
            current_time('mysql'),
296 1
            strtoupper($levelName),
297 1
            $backtraceLine,
298 1
            $message
299
        );
300
    }
301
302
    /**
303
     * @param int $level
304
     * @return bool
305
     */
306 1
    protected function canLogEntry($level, $backtraceLine)
307
    {
308 1
        $levelExists = array_key_exists($level, $this->getLevels());
309 1
        if (false === strpos($backtraceLine, glsr()->path())) {
310
            return $levelExists; // ignore level restriction if triggered outside of the plugin
311
        }
312 1
        return $levelExists && $level >= $this->getLevel();
313
    }
314
315
    /**
316
     * @return void|string
317
     */
318 1
    protected function getBacktraceLine()
319
    {
320 1
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6);
321 1
        $search = array_search('log', glsr_array_column($backtrace, 'function'));
322 1
        if (false !== $search) {
323 1
            $index = '{closure}' == glsr_get($backtrace, ($search + 2).'.function')
324
                ? $search + 4
325 1
                : $search + 1;
326 1
            return $this->buildBacktraceLine($backtrace, $index);
327
        }
328
        return 'Unknown';
329
    }
330
331
    /**
332
     * @param mixed $data
333
     * @return string
334
     */
335
    protected function getBacktraceLineFromData($data)
336
    {
337
        $backtrace = $data instanceof Throwable
338
            ? $data->getTrace()
339
            : debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
340
        return $this->buildBacktraceLine($backtrace, 0);
341
    }
342
343
    /**
344
     * @param mixed $data
345
     * @return string
346
     */
347
    protected function getMessageFromData($data)
348
    {
349
        return $data instanceof Throwable
350
            ? $this->normalizeThrowableMessage($data->getMessage())
351
            : print_r($data, 1);
352
    }
353
354
    /**
355
     * Interpolates context values into the message placeholders.
356
     * @param mixed $message
357
     * @param array $context
358
     * @return string
359
     */
360 1
    protected function interpolate($message, $context = [])
361
    {
362 1
        if ($this->isObjectOrArray($message) || !is_array($context)) {
363
            return print_r($message, true);
364
        }
365 1
        $replace = [];
366 1
        foreach ($context as $key => $value) {
367
            $replace['{'.$key.'}'] = $this->normalizeValue($value);
368
        }
369 1
        return strtr($message, $replace);
370
    }
371
372
    /**
373
     * @param mixed $value
374
     * @return bool
375
     */
376 1
    protected function isObjectOrArray($value)
377
    {
378 1
        return is_object($value) || is_array($value);
379
    }
380
381
    /**
382
     * @param string $backtraceLine
383
     * @return string
384
     */
385 1
    protected function normalizeBacktraceLine($backtraceLine)
386
    {
387
        $search = [
388 1
            glsr()->path('plugin/'),
389 1
            glsr()->path('plugin/', false),
390 1
            trailingslashit(glsr()->path()),
391 1
            trailingslashit(glsr()->path('', false)),
392 1
            WP_CONTENT_DIR,
393 1
            ABSPATH,
394
        ];
395 1
        return str_replace(array_unique($search), '', $backtraceLine);
396
    }
397
398
    /**
399
     * @param string $message
400
     * @return string
401
     */
402
    protected function normalizeThrowableMessage($message)
403
    {
404
        $calledIn = strpos($message, ', called in');
405
        return false !== $calledIn
406
            ? substr($message, 0, $calledIn)
407
            : $message;
408
    }
409
410
    /**
411
     * @param mixed $value
412
     * @return string
413
     */
414
    protected function normalizeValue($value)
415
    {
416
        if ($value instanceof DateTime) {
417
            $value = $value->format('Y-m-d H:i:s');
418
        } elseif ($this->isObjectOrArray($value)) {
419
            $value = json_encode($value);
420
        }
421
        return (string) $value;
422
    }
423
424
    /**
425
     * @return void
426
     */
427 1
    protected function reset()
428
    {
429 1
        if ($this->size() <= pow(1024, 2) / 8) {
430 1
            return;
431
        }
432
        $this->clear();
433
        file_put_contents(
434
            $this->file,
435
            $this->buildLogEntry(
436
                static::NOTICE,
437
                __('Console was automatically cleared (128 KB maximum size)', 'site-reviews')
438
            )
439
        );
440
    }
441
}
442