Completed
Push — master ( ab5aec...dc8798 )
by Raffael
01:44
created

Log::getDefaultAdapter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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
use \Micro\Container\AdapterAwareInterface;
22
23
class Log extends AbstractLogger implements LoggerInterface, AdapterAwareInterface
24
{
25
    /**
26
     * Priorities
27
     */
28
    const PRIORITIES = [
29
        LogLevel::EMERGENCY => 0,
30
        LogLevel::ALERT     => 1,
31
        LogLevel::CRITICAL  => 2,
32
        LogLevel::ERROR     => 3,
33
        LogLevel::WARNING   => 4,
34
        LogLevel::NOTICE    => 5,
35
        LogLevel::INFO      => 6,
36
        LogLevel::DEBUG     => 7,
37
    ];
38
39
40
    /**
41
     * Adapters
42
     *
43
     * @var array
44
     */
45
    protected $adapter = [];
46
47
48
    /**
49
     * static context
50
     *
51
     * @var array
52
     */
53
    protected $context = [];
54
55
56
    /**
57
     * Initialize logger
58
     *
59
     * @param   Iterable $config
60
     * @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...
61
     */
62
    public function __construct(? Iterable $config = null)
63
    {
64
        $this->setOptions($config);
65
    }
66
67
68
    /**
69
     * Set options
70
     *
71
     * @param  Iterable $config
72
     * @return Log
73
     */
74
    public function setOptions(? Iterable $config = null): Log
75
    {
76
        if ($config === null) {
77
            return $this;
78
        }
79
80
        foreach ($config as $option => $value) {
81
            switch($option) {
82
                case 'adapter':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
83
                    foreach($value as $name => $adapter) {
84
                        $this->injectAdapter($name, $adapter);
85
                    }
86
                break;
87
                default:
88
                    throw new Exception('invalid option '.$option.' given');
89
            }
90
        }
91
92
        return $this;
93
    }
94
95
96
    /**
97
     * Get default adapter
98
     *
99
     * @return array
100
     */
101
    public function getDefaultAdapter(): array
102
    {
103
        return [];
104
    }
105
106
107
    /**
108
     * Has adapter
109
     *
110
     * @param  string $name
111
     * @return bool
112
     */
113
    public function hasAdapter(string $name): bool
114
    {
115
        return isset($this->adapter[$name]);
116
    }
117
118
119
    /**
120
     * Add adapter
121
     *
122
     * @param  string $name
123
     * @param  string $class
124
     * @param  Iterable $config
125
     * @return AdapterInterface
126
     */
127
    public function addAdapter(string $name, string $class, ? Iterable $config = null) : AdapterInterface
128
    {
129
        if ($this->hasAdapter($name)) {
130
            throw new Exception('log adapter '.$name.' is already registered');
131
        }
132
133
        $adapter = new $class($config);
134
        if (!($adapter instanceof AdapterInterface)) {
135
            throw new Exception('log adapter must include AdapterInterface interface');
136
        }
137
        $this->adapter[$name] = $adapter;
138
        return $adapter;
139
    }
140
141
142
    /**
143
     * Inject adapter
144
     *
145
     * @param  string $name
146
     * @param  AdapterInterface $adapter
147
     * @return AdapterInterface
148
     */
149
    public function injectAdapter(string $name, AdapterInterface $adapter) : AdapterInterface
150
    {
151
        if ($this->hasAdapter($name)) {
152
            throw new Exception('log adapter '.$name.' is already registered');
153
        }
154
155
        $this->adapter[$name] = $adapter;
156
        return $adapter;
157
    }
158
159
160
    /**
161
     * Get adapter
162
     *
163
     * @param  string $name
164
     * @return AdapterInterface
165
     */
166
    public function getAdapter(string $name): AdapterInterface
167
    {
168
        if (!$this->hasAdapter($name)) {
169
            throw new Exception('log adapter '.$name.' is not registered');
170
        }
171
172
        return $this->adapter[$name];
173
    }
174
175
176
    /**
177
     * Get adapters
178
     *
179
     * @param  array $adapters
180
     * @return array
181
     */
182
    public function getAdapters(array $adapters = []): array
183
    {
184
        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...
185
            return $this->adapter;
186
        } else {
187
            $list = [];
188
            foreach ($adapter as $name) {
189
                if (!$this->hasAdapter($name)) {
190
                    throw new Exception('log adapter '.$name.' is not registered');
191
                }
192
                $list[$name] = $this->adapter[$name];
193
            }
194
195
            return $list;
196
        }
197
    }
198
199
200
    /**
201
     * Log message
202
     *
203
     * @param   string $level
204
     * @param   string $message
205
     * @param   array $context
206
     * @return  bool
207
     */
208
    public function log($level, $message, array $context = []): bool
209
    {
210
        if (!array_key_exists($level, self::PRIORITIES)) {
211
            throw new Exception('log level '.$level.' is unkown');
212
        }
213
214
        foreach ($this->adapter as $adapter) {
215
            $prio = $adapter->getLevel();
216
217
            if (self::PRIORITIES[$level] <= $prio) {
218
                $msg = $this->_format($message, $adapter->getFormat(), $adapter->getDateFormat(), $level, $context);
219
                $adapter->log($level, $msg);
220
            }
221
        }
222
223
        return true;
224
    }
225
226
227
    /**
228
     *  Add static context
229
     *
230
     * @param  string $name
231
     * @param  string $value
232
     * @return Log
233
     */
234
    public function addContext(string $name, string $value): Log
235
    {
236
        $this->context[$name] = $value;
237
        return $this;
238
    }
239
240
241
    /**
242
     * Log message
243
     *
244
     * @param   string $message
245
     * @param   string $format
246
     * @param   string $date_format
247
     * @param   string $level
248
     * @param   array $context
249
     * @return  string
250
     */
251
    protected function _format(string $message, string $format, string $date_format, string $level, array $context = []): string
252
    {
253
        $parsed = preg_replace_callback('/(\{(([a-z]\.*)+)\})/', function($match) use ($message, $level, $date_format, $context) {
254
            $key = '';
255
            $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...
256
257
            if ($sub_context = strpos($match[2], '.')) {
258
                $parts = explode('.', $match[2]);
259
                $name = $parts[0];
260
                $key = $parts[1];
261
            } else {
262
                $name = $match[2];
263
            }
264
265
            switch ($name) {
266
                case 'level':
267
                    return $match[0] = $level;
268
                    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...
269
                case 'date':
270
                    return $match[0] = date($date_format);
271
                    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...
272
                case 'message':
273
                    $replace = [];
274
                    foreach ($context as $key => $val) {
275
                        if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
276
                            $replace['{'.$key.'}'] = $val;
277
                        } else {
278
                            $replace['{'.$key.'}'] = json_encode($val);
279
                        }
280
                    }
281
282
                    return $match[0] = strtr($message, $replace);
283
                    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...
284
                case 'context':
285
                    if ($sub_context) {
286
                        if (array_key_exists($key, $context)) {
287
                            if (!is_array($context[$key]) && (!is_object($context[$key]) || method_exists($context[$key], '__toString'))) {
288
                                return $match[0] = $context[$key];
289
                            } else {
290
                                return $match[0] = json_encode($context[$key]);
291
                            }
292
                        }
293
                    } else {
294
                        return $match[0] = json_encode($context);
295
                    }
296
                    break;
297
            }
298
        }, $format);
299
300
        return $parsed;
301
    }
302
}
303