Completed
Push — master ( 091d0a...0daa93 )
by Raffael
01:36
created

Log::setOptions()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 13
nc 6
nop 1
1
<?php
2
declare(strict_types = 1);
3
4
/**
5
 * Micro
6
 *
7
 * @author    Raffael Sahli <[email protected]>
8
 * @copyright Copyright (c) 2017 gyselroth GmbH (https://gyselroth.com)
9
 * @license   MIT https://opensource.org/licenses/MIT
10
 */
11
12
namespace Micro;
13
14
use \Psr\Log\AbstractLogger;
15
use \Psr\Log\LogLevel;
16
use \Psr\Log\LoggerInterface;
17
use \Micro\Log\Adapter\AbstractAdapter;
18
use \Micro\Log\Adapter\AdapterInterface;
19
use \Micro\Config;
20
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...
21
22
class Log extends AbstractLogger implements LoggerInterface
23
{
24
    /**
25
     * Priorities
26
     */
27
    const PRIORITIES = [
28
        LogLevel::EMERGENCY => 0,
29
        LogLevel::ALERT     => 1,
30
        LogLevel::CRITICAL  => 2,
31
        LogLevel::ERROR     => 3,
32
        LogLevel::WARNING   => 4,
33
        LogLevel::NOTICE    => 5,
34
        LogLevel::INFO      => 6,
35
        LogLevel::DEBUG     => 7,
36
    ];
37
38
39
    /**
40
     * Adapters
41
     *
42
     * @var array
43
     */
44
    protected $adapter = [];
45
46
47
    /**
48
     * static context
49
     *
50
     * @var array
51
     */
52
    protected $context = [];
53
54
55
    /**
56
     * Initialize logger
57
     *
58
     * @param   Iterable $config
59
     * @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...
60
     */
61
    public function __construct(? Iterable $config = null)
62
    {
63
        $this->setOptions($config);
64
    }
65
66
67
    /**
68
     * Set options
69
     *
70
     * @param  Iterable $config
71
     * @return Log
72
     */
73
    public function setOptions(? Iterable $config = null): Log
74
    {
75
        if ($config === null) {
76
            return $this;
77
        }
78
79
        foreach ($config as $option => $value) {
80
            if (!isset($value['enabled']) || $value['enabled'] === '1') {
81
                if(!isset($value['class'])) {
82
                    throw new Exception('class option is requred');
83
                }
84
85
                if(isset($value['config'])) {
86
                    $config = $value['config'];
87
                } else {
88
                    $config = null;
89
                }
90
91
                $this->addAdapter($option, $value['class'], $value['config']);
92
            }
93
        }
94
        
95
        return $this;
96
    }
97
98
99
    /**
100
     * Has adapter
101
     *
102
     * @param  string $name
103
     * @return bool
104
     */
105
    public function hasAdapter(string $name): bool
106
    {
107
        return isset($this->adapter[$name]);
108
    }
109
110
111
    /**
112
     * Add adapter
113
     *
114
     * @param  string $name
115
     * @param  string $class
116
     * @param  Iterable $config
117
     * @return AdapterInterface
118
     */
119
    public function addAdapter(string $name, string $class, ? Iterable $config = null) : AdapterInterface
120
    {
121
        if ($this->hasAdapter($name)) {
122
            throw new Exception('log adapter '.$name.' is already registered');
123
        }
124
            
125
        $adapter = new $class($config);
126
        if (!($adapter instanceof AdapterInterface)) {
127
            throw new Exception('log adapter must include AdapterInterface interface');
128
        }
129
        $this->adapter[$name] = $adapter;
130
        return $adapter;
131
    }
132
133
134
    /**
135
     * Get adapter
136
     *      
137
     * @param  string $name
138
     * @return AdapterInterface
139
     */
140
    public function getAdapter(string $name): AdapterInterface
141
    {
142
        if (!$this->hasAdapter($name)) {
143
            throw new Exception('log adapter '.$name.' is not registered');
144
        }
145
146
        return $this->adapter[$name];
147
    }
148
149
150
    /**
151
     * Get adapters
152
     *      
153
     * @param  array $adapters
154
     * @return array
155
     */
156
    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...
157
    {
158
        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...
159
            return $this->adapter;
160
        } else {
161
            $list = [];
162
            foreach ($adapter as $name) {
163
                if (!$this->hasAdapter($name)) {
164
                    throw new Exception('log adapter '.$name.' is not registered');
165
                }
166
                $list[$name] = $this->adapter[$name];
167
            }
168
169
            return $list;
170
        }
171
    }
172
173
    
174
    /**
175
     * Log message
176
     *
177
     * @param   string $level
178
     * @param   string $message
179
     * @param   array $context
180
     * @return  bool
181
     */
182
    public function log($level, $message, array $context = []): bool
183
    {
184
        if (!array_key_exists($level, self::PRIORITIES)) {
185
            throw new Exception('log level '.$level.' is unkown');
186
        }
187
188
        foreach ($this->adapter as $adapter) {
189
            $prio = $adapter->getLevel();
190
 
191
            if (self::PRIORITIES[$level] <= $prio) {
192
                $msg = $this->_format($message, $adapter->getFormat(), $adapter->getDateFormat(), $level, $context);
193
                $adapter->log($level, $msg);
194
            }
195
        }
196
197
        return true;
198
    }
199
  
200
201
    /**
202
     *  Add static context
203
     *
204
     * @param  string $name
205
     * @param  string $value
206
     * @return Log
207
     */
208
    public function addContext(string $name, string $value): Log
209
    {
210
        $this->context[$name] = $value;
211
        return $this;
212
    }
213
214
215
    /**
216
     * Log message
217
     *
218
     * @param   string $message
219
     * @param   string $format
220
     * @param   string $date_format
221
     * @param   string $level
222
     * @param   array $context
223
     * @return  string
224
     */
225
    protected function _format(string $message, string $format, string $date_format, string $level, array $context = []): string
226
    {
227
        $parsed = preg_replace_callback('/(\{(([a-z]\.*)+)\})/', function($match) use ($message, $level, $date_format, $context) {
228
            $key = '';
229
            $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...
230
                    
231
            if ($sub_context = strpos($match[2], '.')) {
232
                $parts = explode('.', $match[2]);
233
                $name = $parts[0];
234
                $key = $parts[1];
235
            } else {
236
                $name = $match[2];
237
            }
238
            
239
            switch ($name) {
240
                case 'level':
241
                    return $match[0] = $level;
242
                    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...
243
                case 'date':
244
                    return $match[0] = date($date_format);
245
                    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...
246
                case 'message':
247
                    $replace = [];
248
                    foreach ($context as $key => $val) {
249
                        if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
250
                            $replace['{'.$key.'}'] = $val;
251
                        } else {
252
                            $replace['{'.$key.'}'] = json_encode($val);
253
                        }
254
                    }
255
256
                    return $match[0] = strtr($message, $replace);
257
                    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...
258
                case 'context':
259
                    if ($sub_context) {
260
                        if (array_key_exists($key, $context)) {
261
                            if (!is_array($context[$key]) && (!is_object($context[$key]) || method_exists($context[$key], '__toString'))) {
262
                                return $match[0] = $context[$key];
263
                            } else {
264
                                return $match[0] = json_encode($context[$key]);
265
                            }
266
                        }
267
                    } else {
268
                        return $match[0] = json_encode($context);
269
                    }
270
                    break;
271
            }
272
        }, $format);
273
        
274
        return $parsed;
275
    }
276
}
277