Completed
Push — master ( 76ae44...61569d )
by Raffael
01:46
created

Log::injectAdapter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 2
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
     * Inject adapter
136
     *
137
     * @param  string $name
138
     * @param  AdapterInterface $adapter
139
     * @return AdapterInterface
140
     */
141
    public function injectAdapter(string $name, AdapterInterface $adapter) : AdapterInterface
142
    {
143
        if ($this->hasAdapter($name)) {
144
            throw new Exception('log adapter '.$name.' is already registered');
145
        }
146
            
147
        $this->adapter[$name] = $adapter;
148
        return $adapter;
149
    }
150
151
152
    /**
153
     * Get adapter
154
     *      
155
     * @param  string $name
156
     * @return AdapterInterface
157
     */
158
    public function getAdapter(string $name): AdapterInterface
159
    {
160
        if (!$this->hasAdapter($name)) {
161
            throw new Exception('log adapter '.$name.' is not registered');
162
        }
163
164
        return $this->adapter[$name];
165
    }
166
167
168
    /**
169
     * Get adapters
170
     *      
171
     * @param  array $adapters
172
     * @return array
173
     */
174
    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...
175
    {
176
        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...
177
            return $this->adapter;
178
        } else {
179
            $list = [];
180
            foreach ($adapter as $name) {
181
                if (!$this->hasAdapter($name)) {
182
                    throw new Exception('log adapter '.$name.' is not registered');
183
                }
184
                $list[$name] = $this->adapter[$name];
185
            }
186
187
            return $list;
188
        }
189
    }
190
191
    
192
    /**
193
     * Log message
194
     *
195
     * @param   string $level
196
     * @param   string $message
197
     * @param   array $context
198
     * @return  bool
199
     */
200
    public function log($level, $message, array $context = []): bool
201
    {
202
        if (!array_key_exists($level, self::PRIORITIES)) {
203
            throw new Exception('log level '.$level.' is unkown');
204
        }
205
206
        foreach ($this->adapter as $adapter) {
207
            $prio = $adapter->getLevel();
208
 
209
            if (self::PRIORITIES[$level] <= $prio) {
210
                $msg = $this->_format($message, $adapter->getFormat(), $adapter->getDateFormat(), $level, $context);
211
                $adapter->log($level, $msg);
212
            }
213
        }
214
215
        return true;
216
    }
217
  
218
219
    /**
220
     *  Add static context
221
     *
222
     * @param  string $name
223
     * @param  string $value
224
     * @return Log
225
     */
226
    public function addContext(string $name, string $value): Log
227
    {
228
        $this->context[$name] = $value;
229
        return $this;
230
    }
231
232
233
    /**
234
     * Log message
235
     *
236
     * @param   string $message
237
     * @param   string $format
238
     * @param   string $date_format
239
     * @param   string $level
240
     * @param   array $context
241
     * @return  string
242
     */
243
    protected function _format(string $message, string $format, string $date_format, string $level, array $context = []): string
244
    {
245
        $parsed = preg_replace_callback('/(\{(([a-z]\.*)+)\})/', function($match) use ($message, $level, $date_format, $context) {
246
            $key = '';
247
            $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...
248
                    
249
            if ($sub_context = strpos($match[2], '.')) {
250
                $parts = explode('.', $match[2]);
251
                $name = $parts[0];
252
                $key = $parts[1];
253
            } else {
254
                $name = $match[2];
255
            }
256
            
257
            switch ($name) {
258
                case 'level':
259
                    return $match[0] = $level;
260
                    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...
261
                case 'date':
262
                    return $match[0] = date($date_format);
263
                    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...
264
                case 'message':
265
                    $replace = [];
266
                    foreach ($context as $key => $val) {
267
                        if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
268
                            $replace['{'.$key.'}'] = $val;
269
                        } else {
270
                            $replace['{'.$key.'}'] = json_encode($val);
271
                        }
272
                    }
273
274
                    return $match[0] = strtr($message, $replace);
275
                    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...
276
                case 'context':
277
                    if ($sub_context) {
278
                        if (array_key_exists($key, $context)) {
279
                            if (!is_array($context[$key]) && (!is_object($context[$key]) || method_exists($context[$key], '__toString'))) {
280
                                return $match[0] = $context[$key];
281
                            } else {
282
                                return $match[0] = json_encode($context[$key]);
283
                            }
284
                        }
285
                    } else {
286
                        return $match[0] = json_encode($context);
287
                    }
288
                    break;
289
            }
290
        }, $format);
291
        
292
        return $parsed;
293
    }
294
}
295