Completed
Push — master ( b659c5...c65770 )
by Michael
10:22
created

LogWiring.php$1 ➔ __invoke()   A

Complexity

Conditions 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 2
crap 6
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-2017 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-2017 Michael Cummings
32
 * @license   LGPL-3.0+
33
 * @author    Michael Cummings <[email protected]>
34
 */
35
namespace Yapeal\Configuration;
36
37
use Monolog\Formatter\FormatterInterface;
38
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
39
use Monolog\Handler\HandlerInterface;
40
use Psr\Log\LoggerInterface;
41
use Psr\Log\LogLevel;
42
use Yapeal\Container\ContainerInterface;
43
use Yapeal\Log\Logger;
44
45
/**
46
 * Class LogWiring.
47
 */
48
class LogWiring implements WiringInterface
49
{
50
    /**
51
     * @param ContainerInterface $dic
52
     */
53
    public function wire(ContainerInterface $dic)
54
    {
55
        $this->wireLineFormatter($dic)
56
            ->wireCli($dic)
57
            ->wireFileSystem($dic)
58
            ->wireGroup($dic)
59
            ->wireStrategy($dic)
60
            ->wireFingersCrossed($dic)
61
            ->wireLogger($dic)
62
            ->registerErrorHandler($dic)
63
            ->registerExceptionHandler($dic)
64
            ->registerFatalHandler($dic);
65
        /**
66
         * @var \Yapeal\Event\MediatorInterface $mediator
67
         */
68
        $mediator = $dic['Yapeal.Event.Callable.Mediator'];
69
        $mediator->addServiceListener('Yapeal.Log.log', ['Yapeal.Log.Callable.Logger', 'logEvent'], 'last');
70
        $mediator->addServiceListener('Yapeal.Log.error', ['Yapeal.Log.Callable.Logger', 'logEvent'], 'last');
71
    }
72
    /**
73
     * @param ContainerInterface $dic
74
     *
75
     * @return self Fluent interface.
76
     */
77
    private function registerErrorHandler(ContainerInterface $dic): self
78
    {
79
        $errorLevelMap = $dic['Yapeal.Log.Parameters.Register.errorLevelMap'];
80
        if (false !== $errorLevelMap) {
81
            $errorLevelMap = (array)json_decode($errorLevelMap);
82
            /** @noinspection PhpTooManyParametersInspection */
83
            $errorHandler = new class($dic['Yapeal.Log.Callable.Logger'], $errorLevelMap)
84
            {
85
                /**
86
                 *  constructor.
87
                 *
88
                 * @param LoggerInterface $logger
89
                 * @param array           $errorLevelMap
90
                 */
91
                public function __construct(LoggerInterface $logger, array $errorLevelMap = [])
92
                {
93
                    $this->logger = $logger;
94
                    $this->errorLevelMap = $errorLevelMap;
95
                }
96
                /**
97
                 * @param int $code
98
                 * @param string $message
99
                 * @param string $file
100
                 * @param int $line
101
                 * @param array $context
102
                 *
103
                 * @return bool
104
                 */
105
                public function __invoke(
106
                    int $code,
107
                    string $message,
108
                    string $file = '',
109
                    int $line = 0,
110
                    array $context = []
111
                ): bool {
112
                    if (!(error_reporting() & $code)) {
113
                        return false;
114
                    }
115
                    if ($code & self::HANDLE_ERRORS) {
116
                        $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;
117
                        $this->logger->log($level,
118
                            $message,
119
                            [
120
                                'code' => $code,
121
                                'message' => $message,
122
                                'file' => str_replace('\\', '/', $file),
123
                                'line' => $line
124
                            ]);
125
                    }
126
                    if ($this->previousErrorHandler === true) {
127
                        return false;
128
                    } elseif ($this->previousErrorHandler) {
129
                        return (bool)call_user_func($this->previousErrorHandler,
130
                            $code,
131
                            $message,
132
                            $file,
133
                            $line,
134
                            $context);
135
                    }
136
                    return true;
137
                }
138
                /**
139
                 * @var callable|true $previousErrorHandler
140
                 */
141
                public $previousErrorHandler;
142
                /**
143
                 * @var array $errorLevelMap
144
                 */
145
                private $errorLevelMap;
146
                /**
147
                 * @var LoggerInterface $logger
148
                 */
149
                private $logger;
150
                const IGNORED_ERRORS = E_COMPILE_ERROR
151
                | E_COMPILE_WARNING
152
                | E_CORE_ERROR
153
                | E_CORE_WARNING
154
                | E_ERROR
155
                | E_PARSE
156
                | E_USER_ERROR;
157
                const HANDLE_ERRORS = E_ALL & ~self::IGNORED_ERRORS;
158
            };
159
            $prev = set_error_handler($errorHandler, $errorHandler::HANDLE_ERRORS);
160
            $errorHandler->previousErrorHandler = $prev;
161
        }
162
        return $this;
163
    }
164
    /**
165
     * @param ContainerInterface $dic
166
     *
167
     * @return self Fluent interface.
168
     */
169
    private function registerExceptionHandler(ContainerInterface $dic): self
170
    {
171
        $exceptionLevel = $dic['Yapeal.Log.Parameters.Register.exceptionLevel'];
172
        if (false !== $exceptionLevel) {
173
            $exceptionHandler = new class($dic['Yapeal.Log.Callable.Logger'], $exceptionLevel)
174
            {
175
                /**
176
                 *  constructor.
177
                 *
178
                 * @param LoggerInterface $logger
179
                 * @param int|null        $uncaughtExceptionLevel
180
                 */
181
                public function __construct(LoggerInterface $logger, int $uncaughtExceptionLevel)
182
                {
183
                    $this->logger = $logger;
184
                    $this->uncaughtExceptionLevel = $uncaughtExceptionLevel;
185
                }
186
                /**
187
                 * @param \Throwable $exc
188
                 */
189
                public function __invoke(\Throwable $exc)
190
                {
191
                    $level = $this->uncaughtExceptionLevel ?? LogLevel::ERROR;
192
                    $this->logger->log($level,
193
                        sprintf('Uncaught Exception %s: "%s" at %s line %s',
194
                            get_class($exc),
195
                            $exc->getMessage(),
196
                            str_replace('\\', '/', $exc->getFile()),
197
                            $exc->getLine()),
198
                        ['exception' => $exc]);
199
                    if (null !== $this->previousExceptionHandler) {
200
                        call_user_func($this->previousExceptionHandler, $exc);
201
                    }
202
                    exit(255);
203
                }
204
                /**
205
                 * @var string|null $previousExceptionHandler
206
                 */
207
                public $previousExceptionHandler;
208
                /**
209
                 * @var LoggerInterface $logger
210
                 */
211
                private $logger;
212
                /**
213
                 * @var int|null $uncaughtExceptionLevel
214
                 */
215
                private $uncaughtExceptionLevel;
216
            };
217
            $prev = set_exception_handler($exceptionHandler);
218
            $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...
219
        }
220
        return $this;
221
    }
222
    /**
223
     * @param ContainerInterface $dic
224
     *
225
     * @return self Fluent interface.
226
     */
227
    private function registerFatalHandler(ContainerInterface $dic): self
228
    {
229
        $fatalLevel = $dic['Yapeal.Log.Parameters.Register.fatalLevel'];
230
        if (false !== $fatalLevel) {
231
            $fatalHandler = new class($dic['Yapeal.Log.Callable.Logger'], $fatalLevel)
232
            {
233
                /**
234
                 *  constructor.
235
                 *
236
                 * @param LoggerInterface $logger
237
                 * @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...
238
                 * @param int             $reservedMemorySize
239
                 */
240
                public function __construct(LoggerInterface $logger, int $level = null, int $reservedMemorySize = 20)
241
                {
242
                    $this->logger = $logger;
243
                    $this->fatalLevel = $level;
244
                    $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
245
                }
246
                public function __invoke()
247
                {
248
                    $this->reservedMemory = null;
249
                    $lastError = error_get_last();
250
                    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...
251
                        $this->logger->log($this->fatalLevel ?? LogLevel::ALERT,
252
                            $lastError['message'],
253
                            [
254
                                'code' => $lastError['type'],
255
                                'message' => $lastError['message'],
256
                                'file' => str_replace('\\', '/', $lastError['file']),
257
                                'line' => $lastError['line']
258
                            ]);
259
                        if (method_exists($this->logger, 'getHandlers')) {
260
                            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...
261
                                if (method_exists($handler, 'close')) {
262
                                    $handler->close();
263
                                }
264
                            }
265
                        }
266
                    }
267
                }
268
                const FATAL_ERRORS = E_COMPILE_ERROR
269
                | E_COMPILE_WARNING
270
                | E_CORE_ERROR
271
                | E_CORE_WARNING
272
                | E_ERROR
273
                | E_PARSE
274
                | E_USER_ERROR;
275
                /**
276
                 * @var int|null $fatalLevel
277
                 */
278
                private $fatalLevel;
279
                /**
280
                 * @var LoggerInterface $logger
281
                 */
282
                private $logger;
283
                /**
284
                 * @var string $reservedMemory
285
                 */
286
                private $reservedMemory;
287
            };
288
            register_shutdown_function($fatalHandler);
289
        }
290
        return $this;
291
    }
292
    /**
293
     * @param ContainerInterface $dic
294
     *
295
     * @return self Fluent interface.
296
     */
297
    private function wireCli(ContainerInterface $dic): self
298
    {
299
        if (empty($dic['Yapeal.Log.Callable.Cli'])) {
300
            /**
301
             * @param ContainerInterface $dic
302
             *
303
             * @return HandlerInterface
304
             */
305
            $dic['Yapeal.Log.Callable.Cli'] = function (ContainerInterface $dic): HandlerInterface {
306
                $parameters = [
307
                    $dic['Yapeal.Log.Parameters.Cli.stream'],
308
                    $dic['Yapeal.Log.Parameters.Cli.level'],
309
                    $dic['Yapeal.Log.Parameters.Cli.bubble'],
310
                    $dic['Yapeal.Log.Parameters.Cli.filePermission'],
311
                    $dic['Yapeal.Log.Parameters.Cli.useLocking']
312
                ];
313
                /**
314
                 * @var \Yapeal\Log\StreamHandler $stream
315
                 */
316
                $stream = new $dic['Yapeal.Log.Classes.stream'](...$parameters);
317
                $stream->setPreserve($dic['Yapeal.Log.Parameters.Cli.preserve']);
318
                $lineFormatter = $dic['Yapeal.Log.Parameters.Cli.lineFormatter'];
319
                $stream->setFormatter($dic[$lineFormatter]);
320
                return $stream;
321
            };
322
        }
323
        return $this;
324
    }
325
    /**
326
     * @param ContainerInterface $dic
327
     *
328
     * @return self Fluent interface.
329
     */
330
    private function wireFileSystem(ContainerInterface $dic): self
331
    {
332
        if (empty($dic['Yapeal.Log.Callable.FileSystem'])) {
333
            /**
334
             * @param ContainerInterface $dic
335
             *
336
             * @return HandlerInterface
337
             */
338
            $dic['Yapeal.Log.Callable.FileSystem'] = function (ContainerInterface $dic): HandlerInterface {
339
                $parameters = [
340
                    $dic['Yapeal.Log.Parameters.FileSystem.stream'],
341
                    $dic['Yapeal.Log.Parameters.FileSystem.level'],
342
                    $dic['Yapeal.Log.Parameters.FileSystem.bubble'],
343
                    $dic['Yapeal.Log.Parameters.FileSystem.filePermission'],
344
                    $dic['Yapeal.Log.Parameters.FileSystem.useLocking']
345
                ];
346
                /**
347
                 * @var \Yapeal\Log\StreamHandler $stream
348
                 */
349
                $stream = new $dic['Yapeal.Log.Classes.stream'](...$parameters);
350
                $stream->setPreserve($dic['Yapeal.Log.Parameters.FileSystem.preserve']);
351
                $lineFormatter = $dic['Yapeal.Log.Parameters.FileSystem.lineFormatter'];
352
                $stream->setFormatter($dic[$lineFormatter]);
353
                return $stream;
354
            };
355
        }
356
        return $this;
357
    }
358
    /**
359
     * @param ContainerInterface $dic
360
     *
361
     * @return self Fluent interface.
362
     */
363
    private function wireFingersCrossed(ContainerInterface $dic): self
364
    {
365
        if (empty($dic['Yapeal.Log.Callable.FingersCrossed'])) {
366
            /**
367
             * @param ContainerInterface $dic
368
             *
369
             * @return HandlerInterface
370
             */
371
            $dic['Yapeal.Log.Callable.FingersCrossed'] = function (ContainerInterface $dic): HandlerInterface {
372
                /**
373
                 * @var string $activationStrategy
374
                 * @var string $handler
375
                 * @var array  $parameters
376
                 */
377
                $activationStrategy = $dic['Yapeal.Log.Parameters.FingersCrossed.activationStrategy'];
378
                $handler = $dic['Yapeal.Log.Parameters.FingersCrossed.handler'];
379
                $parameters = [
380
                    $dic[$handler],
381
                    $dic[$activationStrategy],
382
                    $dic['Yapeal.Log.Parameters.FingersCrossed.bufferSize'],
383
                    $dic['Yapeal.Log.Parameters.FingersCrossed.bubble'],
384
                    $dic['Yapeal.Log.Parameters.FingersCrossed.stopBuffering'],
385
                    $dic['Yapeal.Log.Parameters.FingersCrossed.passThruLevel'],
386
                ];
387
                return new $dic['Yapeal.Log.Classes.fingersCrossed'](...$parameters);
388
            };
389
        }
390
        return $this;
391
    }
392
    /**
393
     * @param ContainerInterface $dic
394
     *
395
     * @return self Fluent interface.
396
     */
397
    private function wireGroup(ContainerInterface $dic): self
398
    {
399
        if (empty($dic['Yapeal.Log.Callable.Group'])) {
400
            /**
401
             * @param ContainerInterface $dic
402
             *
403
             * @return HandlerInterface
404
             */
405
            $dic['Yapeal.Log.Callable.Group'] = function (ContainerInterface $dic): HandlerInterface {
406
                $handlers = [];
407
                foreach (explode(',', $dic['Yapeal.Log.Parameters.Group.handlers']) as $handler) {
408
                    if ('' === $handler) {
409
                        continue;
410
                    }
411
                    $handlers[] = $dic[$handler];
412
                }
413
                $parameters = [
414
                    $handlers,
415
                    $bubble = $dic['Yapeal.Log.Parameters.Group.bubble']
416
                ];
417
                return new $dic['Yapeal.Log.Classes.group'](...$parameters);
418
            };
419
        }
420
        return $this;
421
    }
422
    /**
423
     * @param ContainerInterface $dic
424
     *
425
     * @return self Fluent interface.
426
     */
427
    private function wireLineFormatter(ContainerInterface $dic): self
428
    {
429
        if (empty($dic['Yapeal.Log.Callable.LFFactory'])) {
430
            $dic['Yapeal.Log.Callable.LFFactory'] = $dic->factory(
431
            /**
432
             * @param ContainerInterface $dic
433
             *
434
             * @return FormatterInterface
435
             */
436
            function (ContainerInterface $dic): FormatterInterface {
437
            $parameters = [
438
                $dic['Yapeal.Log.Parameters.LineFormatter.format'],
439
                $dic['Yapeal.Log.Parameters.LineFormatter.dateFormat'],
440
                $dic['Yapeal.Log.Parameters.LineFormatter.allowInlineLineBreaks'],
441
                $dic['Yapeal.Log.Parameters.LineFormatter.ignoreEmptyContextAndExtra']
442
            ];
443
            /**
444
             * @var \Yapeal\Log\LineFormatter $lineFormatter
445
             */
446
            $lineFormatter = new $dic['Yapeal.Log.Classes.lineFormatter'](...$parameters);
447
            $lineFormatter->includeStacktraces($dic['Yapeal.Log.Parameters.LineFormatter.includeStackTraces']);
448
            $lineFormatter->setPrettyJson($dic['Yapeal.Log.Parameters.LineFormatter.prettyJson']);
449
            return $lineFormatter;
450
            });
451
        }
452
        if (empty($dic['Yapeal.Log.Callable.CliLF'])) {
453
            $dic['Yapeal.Log.Callable.CliLF'] = $dic['Yapeal.Log.Callable.LFFactory'];
454
        }
455
        if (empty($dic['Yapeal.Log.Callable.FileSystemLF'])) {
456
            $dic['Yapeal.Log.Callable.FileSystemLF'] = $dic['Yapeal.Log.Callable.LFFactory'];
457
        }
458
        return $this;
459
    }
460
    /**
461
     * @param ContainerInterface $dic
462
     *
463
     * @return self Fluent interface.
464
     */
465
    private function wireLogger(ContainerInterface $dic): self
466
    {
467
        if (empty($dic['Yapeal.Log.Callable.Logger'])) {
468
            /**
469
             * @param ContainerInterface $dic
470
             *
471
             * @return Logger
472
             */
473
            $dic['Yapeal.Log.Callable.Logger'] = function (ContainerInterface $dic): Logger {
474
                /**
475
                 * @var \Yapeal\Log\Logger $logger
476
                 */
477
                $handlers = [];
478
                foreach (explode(',', $dic['Yapeal.Log.Parameters.Logger.handlers']) as $handler) {
479
                    if ('' === $handler) {
480
                        continue;
481
                    }
482
                    $handlers[] = $dic[$handler];
483
                }
484
                $processors = [];
485
                foreach (explode(',', $dic['Yapeal.Log.Parameters.Logger.processors']) as $processor) {
486
                    if ('' === $processor) {
487
                        continue;
488
                    }
489
                    $processors[] = $dic[$processor];
490
                }
491
                $parameters = [
492
                    $dic['Yapeal.Log.Parameters.Logger.name'],
493
                    $handlers,
494
                    $processors
495
                ];
496
                $logger = new $dic['Yapeal.Log.Classes.logger'](...$parameters);
497
                return $logger;
498
            };
499
        }
500
        return $this;
501
    }
502
    /**
503
     * @param ContainerInterface $dic
504
     *
505
     * @return self Fluent interface.
506
     */
507
    private function wireStrategy(ContainerInterface $dic): self
508
    {
509
        if (empty($dic['Yapeal.Log.Callable.Strategy'])) {
510
            /**
511
             * @param ContainerInterface $dic
512
             *
513
             * @return ActivationStrategyInterface
514
             */
515
            $dic['Yapeal.Log.Callable.Strategy'] = function (ContainerInterface $dic): ActivationStrategyInterface {
516
                return new $dic['Yapeal.Log.Classes.strategy']((int)$dic['Yapeal.Log.Parameters.Strategy.actionLevel']);
517
            };
518
        }
519
        return $this;
520
    }
521
}
522