Completed
Push — master ( bb5a1d...c5ed6e )
by Raffael
01:42
created

Log   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 2
dl 0
loc 246
rs 8.8
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B setOptions() 0 14 5
A hasAdapter() 0 4 1
A addAdapter() 0 13 3
A getAdapter() 0 8 2
A getAdapters() 0 16 4
A log() 0 17 4
A addContext() 0 5 1
C _format() 0 51 15
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * Micro
6
 *
7
 * @copyright Copyright (c) 2017 gyselroth GmbH (https://gyselroth.com)
8
 * @license   MIT https://opensource.org/licenses/MIT
9
 */
10
11
namespace Micro;
12
13
use \Psr\Log\AbstractLogger;
14
use \Psr\Log\LogLevel;
15
use \Psr\Log\LoggerInterface;
16
use \Micro\Log\Adapter\AbstractAdapter;
17
use \Micro\Log\Adapter\AdapterInterface;
18
use \Micro\Config;
19
use \Micro\Log\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Micro\Exception.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
20
21
class Log extends AbstractLogger implements LoggerInterface
22
{
23
    /**
24
     * Priorities
25
     */
26
    const PRIORITIES = [
27
        LogLevel::EMERGENCY => 0,
28
        LogLevel::ALERT     => 1,
29
        LogLevel::CRITICAL  => 2,
30
        LogLevel::ERROR     => 3,
31
        LogLevel::WARNING   => 4,
32
        LogLevel::NOTICE    => 5,
33
        LogLevel::INFO      => 6,
34
        LogLevel::DEBUG     => 7,
35
    ];
36
37
38
    /**
39
     * Adapters
40
     *
41
     * @var array
42
     */
43
    protected $adapter = [];
44
45
46
    /**
47
     * static context
48
     *
49
     * @var array
50
     */
51
    protected $context = [];
52
53
54
    /**
55
     * Initialize logger
56
     *
57
     * @param   Iterable $config
58
     * @return  void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
59
     */
60
    public function __construct(?Iterable $config=null)
61
    {
62
        $this->setOptions($config);
63
    }
64
65
66
    /**
67
     * Set options
68
     *
69
     * @param  Iterable $config
70
     * @return Log
71
     */
72
    public function setOptions(?Iterable $config=null)
73
    {
74
        if($config === null) {
75
            return $this;
76
        }
77
78
        foreach($config as $option => $value) {
79
            if(!isset($value['enabled']) || $value['enabled'] === '1') {
80
                $this->addAdapter($option, $value['class'], $value['config']);
81
            }
82
        }
83
        
84
        return $this;
85
    }
86
87
88
    /**
89
     * Has adapter
90
     *
91
     * @param  string $name
92
     * @return bool
93
     */
94
    public function hasAdapter(string $name): bool
95
    {
96
        return isset($this->adapter[$name]);
97
    }
98
99
100
    /**
101
     * Add adapter
102
     *
103
     * @param  string $name
104
     * @param  string $class
105
     * @param  Iterable $config
106
     * @return AdapterInterface
107
     */
108
    public function addAdapter(string $name, string $class, ?Iterable $config=null): AdapterInterface
109
    {
110
        if($this->hasAdapter($name)) {
111
            throw new Exception('log adapter '.$name.' is already registered');
112
        }
113
            
114
        $adapter = new $class($config);
115
        if(!($adapter instanceof AdapterInterface)) {
116
            throw new Exception('log adapter must include AdapterInterface interface');
117
        }
118
        $this->adapter[$name] = $adapter;
119
        return $adapter;
120
    }
121
122
123
    /**
124
     * Get adapter
125
     *      
126
     * @param  string $name
127
     * @return AdapterInterface
128
     */
129
    public function getAdapter(string $name): AdapterInterface
130
    {
131
        if(!$this->hasAdapter($name)) {
132
            throw new Exception('log adapter '.$name.' is not registered');
133
        }
134
135
        return $this->adapter[$name];
136
    }
137
138
139
    /**
140
     * Get adapters
141
     *      
142
     * @param  array $adapters
143
     * @return array
144
     */
145
    public function getAdapters(array $adapters=[]): array
0 ignored issues
show
Unused Code introduced by
The parameter $adapters is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
146
    {
147
        if(empty($adapter)) {
0 ignored issues
show
Bug introduced by
The variable $adapter does not exist. Did you mean $adapters?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
148
            return $this->adapter;
149
        } else {
150
            $list = [];
151
            foreach($adapter as $name) {
152
                if(!$this->hasAdapter($name)) {
153
                    throw new Exception('log adapter '.$name.' is not registered');
154
                }
155
                $list[$name] = $this->adapter[$name];
156
            }
157
158
            return $list;
159
        }
160
    }
161
162
163
    
164
    /**
165
     * Log message
166
     *
167
     * @param   string $level
168
     * @param   string $message
169
     * @param   array $context
170
     * @return  bool
171
     */
172
    public function log($level, $message, array $context=[]): bool
173
    {
174
        if (!array_key_exists($level, self::PRIORITIES)) {
175
            throw new Exception('log level '.$level.' is unkown');
176
        }
177
178
        foreach ($this->adapter as $adapter) {
179
            $prio = $adapter->getLevel();
180
 
181
           if (self::PRIORITIES[$level] <= $prio) {
182
                $msg = $this->_format($message, $adapter->getFormat(), $adapter->getDateFormat(), $level, $context);
183
                $adapter->log($level, $msg);
184
            }
185
        }
186
187
        return true;
188
    }
189
  
190
191
    /**
192
     *  Add static context
193
     *
194
     * @param  string $name
195
     * @param  string $value
196
     * @return Log
197
     */
198
    public function addContext(string $name, string $value): Log
199
    {
200
        $this->context[$name] = $value;
201
        return $this;
202
    }
203
204
205
    /**
206
     * Log message
207
     *
208
     * @param   string $message
209
     * @param   string $format
210
     * @param   string $date_format
211
     * @param   string $level
212
     * @param   array $context
213
     * @return  void
214
     */
215
    protected function _format(string $message, string $format, string $date_format, string $level, array $context=[]): string
216
    {
217
        $parsed = preg_replace_callback('/(\{(([a-z]\.*)+)\})/', function ($match) use ($message, $level, $date_format, $context) {
218
            $key = '';
219
            $context = array_merge($this->context, $context);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $context, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
220
                    
221
            if ($sub_context = strpos($match[2], '.')) {
222
                $parts = explode('.', $match[2]);
223
                $name = $parts[0];
224
                $key = $parts[1];
225
            } else {
226
                $name = $match[2];
227
            }
228
            
229
            switch ($name) {
230
                case 'level':
231
                    return $match[0] = $level;
232
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
233
                case 'date':
234
                    return $match[0] = date($date_format);
235
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
236
                case 'message':
237
                    $replace = [];
238
                    foreach ($context as $key => $val) {
239
                        if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
240
                            $replace['{' . $key . '}'] = $val;
241
                        } else {
242
                            $replace['{' . $key . '}'] = json_encode($val);
243
                        }
244
                    }
245
246
                    return $match[0] = strtr($message, $replace);
247
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
248
                case 'context':
249
                    if ($sub_context) {
250
                        if (array_key_exists($key, $context)) {
251
                            if (!is_array($context[$key]) && (!is_object($context[$key]) || method_exists($context[$key], '__toString'))) {
252
                                return $match[0] = $context[$key];
253
                            } else {
254
                                return $match[0] = json_encode($context[$key]);
255
                            }
256
                        }
257
                    } else {
258
                        return $match[0] = json_encode($context);
259
                    }
260
                    break;
261
            }
262
        }, $format);
263
        
264
        return $parsed;
265
    }
266
}
267