Completed
Push — master ( e502a6...b659c5 )
by Michael
08:23
created

LogWiring.php$0 ➔ __invoke()   B

Complexity

Conditions 5

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 33
rs 8.0894
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 5
crap 30
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * Contains class LogWiring.
5
 *
6
 * PHP version 7.0+
7
 *
8
 * LICENSE:
9
 * This file is part of Yet Another Php Eve Api Library also know as Yapeal
10
 * which can be used to access the Eve Online API data and place it into a
11
 * database.
12
 * Copyright (C) 2016 Michael Cummings
13
 *
14
 * This program is free software: you can redistribute it and/or modify it
15
 * under the terms of the GNU Lesser General Public License as published by the
16
 * Free Software Foundation, either version 3 of the License, or (at your
17
 * option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful, but WITHOUT
20
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
22
 * for more details.
23
 *
24
 * You should have received a copy of the GNU Lesser General Public License
25
 * along with this program. If not, see
26
 * <http://spdx.org/licenses/LGPL-3.0.html>.
27
 *
28
 * You should be able to find a copy of this license in the COPYING-LESSER.md
29
 * file. A copy of the GNU GPL should also be available in the COPYING.md file.
30
 *
31
 * @copyright 2016 Michael Cummings
32
 * @license   LGPL-3.0+
33
 * @author    Michael Cummings <[email protected]>
34
 */
35
namespace Yapeal\Configuration;
36
37
use Psr\Log\LoggerInterface;
38
use Psr\Log\LogLevel;
39
use Yapeal\Container\ContainerInterface;
40
41
/**
42
 * Class LogWiring.
43
 */
44
class LogWiring implements WiringInterface
45
{
46
    /**
47
     * @param ContainerInterface $dic
48
     */
49
    public function wire(ContainerInterface $dic)
50
    {
51
        $this->wireLineFormatter($dic)
52
            ->wireCli($dic)
53
            ->wireFileSystem($dic)
54
            ->wireGroup($dic)
55
            ->wireStrategy($dic)
56
            ->wireFingersCrossed($dic)
57
            ->wireLogger($dic)
58
            ->registerErrorHandler($dic)
59
            ->registerExceptionHandler($dic)
60
            ->registerFatalHandler($dic);
61
        /**
62
         * @var \Yapeal\Event\MediatorInterface $mediator
63
         */
64
        $mediator = $dic['Yapeal.Event.Mediator'];
65
        $mediator->addServiceListener('Yapeal.Log.log', ['Yapeal.Log.Callable.Logger', 'logEvent'], 'last');
66
        $mediator->addServiceListener('Yapeal.Log.error', ['Yapeal.Log.Callable.Logger', 'logEvent'], 'last');
67
    }
68
    /**
69
     * @param ContainerInterface $dic
70
     *
71
     * @return self Fluent interface.
72
     */
73
    private function registerErrorHandler(ContainerInterface $dic): self
74
    {
75
        $errorLevelMap = $dic['Yapeal.Log.Parameters.Register.errorLevelMap'];
76
        if (false !== $errorLevelMap) {
77
            $errorLevelMap = (array)json_decode($errorLevelMap);
78
            /** @noinspection PhpTooManyParametersInspection */
79
            $errorHandler = new class($dic['Yapeal.Log.Callable.Logger'], $errorLevelMap)
80
            {
81
                /**
82
                 *  constructor.
83
                 *
84
                 * @param LoggerInterface $logger
85
                 * @param array           $errorLevelMap
86
                 */
87
                public function __construct(LoggerInterface $logger, array $errorLevelMap = [])
88
                {
89
                    $this->logger = $logger;
90
                    $this->errorLevelMap = $errorLevelMap;
91
                }
92
                /**
93
                 * @param int $code
94
                 * @param string $message
95
                 * @param string $file
96
                 * @param int $line
97
                 * @param array $context
98
                 *
99
                 * @return bool
100
                 */
101
                public function __invoke(
102
                    int $code,
103
                    string $message,
104
                    string $file = '',
105
                    int $line = 0,
106
                    array $context = []
107
                ): bool {
108
                    if (!(error_reporting() & $code)) {
109
                        return false;
110
                    }
111
                    if ($code & self::HANDLE_ERRORS) {
112
                        $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;
113
                        $this->logger->log($level,
114
                            $message,
115
                            [
116
                                'code' => $code,
117
                                'message' => $message,
118
                                'file' => str_replace('\\', '/', $file),
119
                                'line' => $line
120
                            ]);
121
                    }
122
                    if ($this->previousErrorHandler === true) {
123
                        return false;
124
                    } elseif ($this->previousErrorHandler) {
125
                        return (bool)call_user_func($this->previousErrorHandler,
126
                            $code,
127
                            $message,
128
                            $file,
129
                            $line,
130
                            $context);
131
                    }
132
                    return true;
133
                }
134
                /**
135
                 * @var callable|true $previousErrorHandler
136
                 */
137
                public $previousErrorHandler;
138
                /**
139
                 * @var array $errorLevelMap
140
                 */
141
                private $errorLevelMap;
142
                /**
143
                 * @var LoggerInterface $logger
144
                 */
145
                private $logger;
146
                const IGNORED_ERRORS = E_COMPILE_ERROR
147
                | E_COMPILE_WARNING
148
                | E_CORE_ERROR
149
                | E_CORE_WARNING
150
                | E_ERROR
151
                | E_PARSE
152
                | E_USER_ERROR;
153
                const HANDLE_ERRORS = E_ALL & ~self::IGNORED_ERRORS;
154
            };
155
            $prev = set_error_handler($errorHandler, $errorHandler::HANDLE_ERRORS);
156
            $errorHandler->previousErrorHandler = $prev;
157
        }
158
        return $this;
159
    }
160
    /**
161
     * @param ContainerInterface $dic
162
     *
163
     * @return self Fluent interface.
164
     */
165
    private function registerExceptionHandler(ContainerInterface $dic): self
166
    {
167
        $exceptionLevel = $dic['Yapeal.Log.Parameters.Register.exceptionLevel'];
168
        if (false !== $exceptionLevel) {
169
            $exceptionHandler = new class($dic['Yapeal.Log.Callable.Logger'], $exceptionLevel)
170
            {
171
                /**
172
                 *  constructor.
173
                 *
174
                 * @param LoggerInterface $logger
175
                 * @param int|null        $uncaughtExceptionLevel
176
                 */
177
                public function __construct(LoggerInterface $logger, int $uncaughtExceptionLevel)
178
                {
179
                    $this->logger = $logger;
180
                    $this->uncaughtExceptionLevel = $uncaughtExceptionLevel;
181
                }
182
                /**
183
                 * @param \Throwable $exc
184
                 */
185
                public function __invoke(\Throwable $exc)
186
                {
187
                    $level = $this->uncaughtExceptionLevel ?? LogLevel::ERROR;
188
                    $this->logger->log($level,
189
                        sprintf('Uncaught Exception %s: "%s" at %s line %s',
190
                            get_class($exc),
191
                            $exc->getMessage(),
192
                            str_replace('\\', '/', $exc->getFile()),
193
                            $exc->getLine()),
194
                        ['exception' => $exc]);
195
                    if (null !== $this->previousExceptionHandler) {
196
                        call_user_func($this->previousExceptionHandler, $exc);
197
                    }
198
                    exit(255);
199
                }
200
                /**
201
                 * @var string|null $previousExceptionHandler
202
                 */
203
                public $previousExceptionHandler;
204
                /**
205
                 * @var LoggerInterface $logger
206
                 */
207
                private $logger;
208
                /**
209
                 * @var int|null $uncaughtExceptionLevel
210
                 */
211
                private $uncaughtExceptionLevel;
212
            };
213
            $prev = set_exception_handler($exceptionHandler);
214
            $exceptionHandler->previousExceptionHandler = $prev;
0 ignored issues
show
Documentation Bug introduced by
It seems like $prev of type callable is incompatible with the declared type string|null of property $previousExceptionHandler.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
215
        }
216
        return $this;
217
    }
218
    /**
219
     * @param ContainerInterface $dic
220
     *
221
     * @return self Fluent interface.
222
     */
223
    private function registerFatalHandler(ContainerInterface $dic)
224
    {
225
        $fatalLevel = $dic['Yapeal.Log.Parameters.Register.fatalLevel'];
226
        if (false !== $fatalLevel) {
227
            $fatalHandler = new class($dic['Yapeal.Log.Callable.Logger'], $fatalLevel)
228
            {
229
                /**
230
                 *  constructor.
231
                 *
232
                 * @param LoggerInterface $logger
233
                 * @param int             $level
0 ignored issues
show
Documentation introduced by
Should the type for parameter $level not be null|integer?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
234
                 * @param int             $reservedMemorySize
235
                 */
236
                public function __construct(LoggerInterface $logger, int $level = null, int $reservedMemorySize = 20)
237
                {
238
                    $this->logger = $logger;
239
                    $this->fatalLevel = $level;
240
                    $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
241
                }
242
                public function __invoke()
243
                {
244
                    $this->reservedMemory = null;
245
                    $lastError = error_get_last();
246
                    if ($lastError && ($lastError['type'] & self::FATAL_ERRORS)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastError of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
247
                        $this->logger->log($this->fatalLevel ?? LogLevel::ALERT,
248
                            $lastError['message'],
249
                            [
250
                                'code' => $lastError['type'],
251
                                'message' => $lastError['message'],
252
                                'file' => str_replace('\\', '/', $lastError['file']),
253
                                'line' => $lastError['line']
254
                            ]);
255
                        if (method_exists($this->logger, 'getHandlers')) {
256
                            foreach ($this->logger->getHandlers() as $handler) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Log\LoggerInterface as the method getHandlers() does only exist in the following implementations of said interface: Monolog\Logger, Yapeal\Log\Logger.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
257
                                if (method_exists($handler, 'close')) {
258
                                    $handler->close();
259
                                }
260
                            }
261
                        }
262
                    }
263
                }
264
                const FATAL_ERRORS = E_COMPILE_ERROR
265
                | E_COMPILE_WARNING
266
                | E_CORE_ERROR
267
                | E_CORE_WARNING
268
                | E_ERROR
269
                | E_PARSE
270
                | E_USER_ERROR;
271
                /**
272
                 * @var int|null $fatalLevel
273
                 */
274
                private $fatalLevel;
275
                /**
276
                 * @var LoggerInterface $logger
277
                 */
278
                private $logger;
279
                /**
280
                 * @var string $reservedMemory
281
                 */
282
                private $reservedMemory;
283
            };
284
            register_shutdown_function($fatalHandler);
285
        }
286
        return $this;
287
    }
288
    /**
289
     * @param ContainerInterface $dic
290
     *
291
     * @return self Fluent interface.
292
     */
293
    private function wireCli(ContainerInterface $dic): self
294
    {
295
        if (empty($dic['Yapeal.Log.Callable.Cli'])) {
296
            $dic['Yapeal.Log.Callable.Cli'] = function () use ($dic) {
297
                $parameters = [
298
                    $dic['Yapeal.Log.Parameters.Cli.stream'],
299
                    $dic['Yapeal.Log.Parameters.Cli.level'],
300
                    $dic['Yapeal.Log.Parameters.Cli.bubble'],
301
                    $dic['Yapeal.Log.Parameters.Cli.filePermission'],
302
                    $dic['Yapeal.Log.Parameters.Cli.useLocking']
303
                ];
304
                /**
305
                 * @var \Yapeal\Log\StreamHandler $stream
306
                 */
307
                $stream = new $dic['Yapeal.Log.Classes.stream'](...$parameters);
308
                $stream->setPreserve($dic['Yapeal.Log.Parameters.Cli.preserve']);
309
                $lineFormatter = $dic['Yapeal.Log.Parameters.Cli.lineFormatter'];
310
                $stream->setFormatter($dic[$lineFormatter]);
311
                return $stream;
312
            };
313
        }
314
        return $this;
315
    }
316
    /**
317
     * @param ContainerInterface $dic
318
     *
319
     * @return self Fluent interface.
320
     */
321
    private function wireFileSystem(ContainerInterface $dic): self
322
    {
323
        if (empty($dic['Yapeal.Log.Callable.FileSystem'])) {
324
            $dic['Yapeal.Log.Callable.FileSystem'] = function () use ($dic) {
325
                $parameters = [
326
                    $dic['Yapeal.Log.Parameters.FileSystem.stream'],
327
                    $dic['Yapeal.Log.Parameters.FileSystem.level'],
328
                    $dic['Yapeal.Log.Parameters.FileSystem.bubble'],
329
                    $dic['Yapeal.Log.Parameters.FileSystem.filePermission'],
330
                    $dic['Yapeal.Log.Parameters.FileSystem.useLocking']
331
                ];
332
                /**
333
                 * @var \Yapeal\Log\StreamHandler $stream
334
                 */
335
                $stream = new $dic['Yapeal.Log.Classes.stream'](...$parameters);
336
                $stream->setPreserve($dic['Yapeal.Log.Parameters.FileSystem.preserve']);
337
                $lineFormatter = $dic['Yapeal.Log.Parameters.FileSystem.lineFormatter'];
338
                $stream->setFormatter($dic[$lineFormatter]);
339
                return $stream;
340
            };
341
        }
342
        return $this;
343
    }
344
    /**
345
     * @param ContainerInterface $dic
346
     *
347
     * @return self Fluent interface.
348
     */
349
    private function wireFingersCrossed(ContainerInterface $dic): self
350
    {
351
        if (empty($dic['Yapeal.Log.Callable.FingersCrossed'])) {
352
            $dic['Yapeal.Log.Callable.FingersCrossed'] = function () use ($dic) {
353
                /**
354
                 * @var string $activationStrategy
355
                 * @var string $handler
356
                 * @var array  $parameters
357
                 */
358
                $activationStrategy = $dic['Yapeal.Log.Parameters.FingersCrossed.activationStrategy'];
359
                $handler = $dic['Yapeal.Log.Parameters.FingersCrossed.handler'];
360
                $parameters = [
361
                    $dic[$handler],
362
                    $dic[$activationStrategy],
363
                    $dic['Yapeal.Log.Parameters.FingersCrossed.bufferSize'],
364
                    $dic['Yapeal.Log.Parameters.FingersCrossed.bubble'],
365
                    $dic['Yapeal.Log.Parameters.FingersCrossed.stopBuffering'],
366
                    $dic['Yapeal.Log.Parameters.FingersCrossed.passThruLevel'],
367
                ];
368
                return new $dic['Yapeal.Log.Classes.fingersCrossed'](...$parameters);
369
            };
370
        }
371
        return $this;
372
    }
373
    /**
374
     * @param ContainerInterface $dic
375
     *
376
     * @return self Fluent interface.
377
     */
378
    private function wireGroup(ContainerInterface $dic): self
379
    {
380
        if (empty($dic['Yapeal.Log.Callable.Group'])) {
381
            $dic['Yapeal.Log.Callable.Group'] = function () use ($dic) {
382
                $handlers = [];
383
                foreach (explode(',', $dic['Yapeal.Log.Parameters.Group.handlers']) as $handler) {
384
                    if ('' === $handler) {
385
                        continue;
386
                    }
387
                    $handlers[] = $dic[$handler];
388
                }
389
                $parameters = [
390
                    $handlers,
391
                    $bubble = $dic['Yapeal.Log.Parameters.Group.bubble']
392
                ];
393
                return new $dic['Yapeal.Log.Classes.group'](...$parameters);
394
            };
395
        }
396
        return $this;
397
    }
398
    /**
399
     * @param ContainerInterface $dic
400
     *
401
     * @return self Fluent interface.
402
     */
403
    private function wireLineFormatter(ContainerInterface $dic): self
404
    {
405
        if (empty($dic['Yapeal.Log.Callable.LFFactory'])) {
406
            $dic['Yapeal.Log.Callable.LFFactory'] = $dic->factory(function () use ($dic) {
407
                $parameters = [
408
                    $dic['Yapeal.Log.Parameters.LineFormatter.format'],
409
                    $dic['Yapeal.Log.Parameters.LineFormatter.dateFormat'],
410
                    $dic['Yapeal.Log.Parameters.LineFormatter.allowInlineLineBreaks'],
411
                    $dic['Yapeal.Log.Parameters.LineFormatter.ignoreEmptyContextAndExtra']
412
                ];
413
                /**
414
                 * @var \Yapeal\Log\LineFormatter $lineFormatter
415
                 */
416
                $lineFormatter = new $dic['Yapeal.Log.Classes.lineFormatter'](...$parameters);
417
                $lineFormatter->includeStacktraces($dic['Yapeal.Log.Parameters.LineFormatter.includeStackTraces']);
418
                $lineFormatter->setPrettyJson($dic['Yapeal.Log.Parameters.LineFormatter.prettyJson']);
419
                return $lineFormatter;
420
            });
421
        }
422
        if (empty($dic['Yapeal.Log.Callable.CliLF'])) {
423
            $dic['Yapeal.Log.Callable.CliLF'] = $dic['Yapeal.Log.Callable.LFFactory'];
424
        }
425
        if (empty($dic['Yapeal.Log.Callable.FileSystemLF'])) {
426
            $dic['Yapeal.Log.Callable.FileSystemLF'] = $dic['Yapeal.Log.Callable.LFFactory'];
427
        }
428
        return $this;
429
    }
430
    /**
431
     * @param ContainerInterface $dic
432
     *
433
     * @return self Fluent interface.
434
     */
435
    private function wireLogger(ContainerInterface $dic)
436
    {
437
        if (empty($dic['Yapeal.Log.Callable.Logger'])) {
438
            $dic['Yapeal.Log.Callable.Logger'] = function () use ($dic) {
439
                /**
440
                 * @var \Yapeal\Log\Logger $logger
441
                 */
442
                $handlers = [];
443
                foreach (explode(',', $dic['Yapeal.Log.Parameters.Logger.handlers']) as $handler) {
444
                    if ('' === $handler) {
445
                        continue;
446
                    }
447
                    $handlers[] = $dic[$handler];
448
                }
449
                $processors = [];
450
                foreach (explode(',', $dic['Yapeal.Log.Parameters.Logger.processors']) as $processor) {
451
                    if ('' === $processor) {
452
                        continue;
453
                    }
454
                    $processors[] = $dic[$processor];
455
                }
456
                $parameters = [
457
                    $dic['Yapeal.Log.Parameters.Logger.name'],
458
                    $handlers,
459
                    $processors
460
                ];
461
                $logger = new $dic['Yapeal.Log.Classes.logger'](...$parameters);
462
                return $logger;
463
            };
464
        }
465
        return $this;
466
    }
467
    /**
468
     * @param ContainerInterface $dic
469
     *
470
     * @return self Fluent interface.
471
     */
472
    private function wireStrategy(ContainerInterface $dic): self
473
    {
474
        if (empty($dic['Yapeal.Log.Callable.Strategy'])) {
475
            $dic['Yapeal.Log.Callable.Strategy'] = function () use ($dic) {
476
                return new $dic['Yapeal.Log.Classes.strategy']((int)$dic['Yapeal.Log.Parameters.Strategy.actionLevel']);
477
            };
478
        }
479
        return $this;
480
    }
481
}
482