Completed
Push — master ( cce0a8...32e652 )
by Neomerx
04:48
created

DefaultHandler::logException()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.5

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 2
cts 4
cp 0.5
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 3
crap 2.5
1
<?php namespace Limoncello\Application\ExceptionHandlers;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use ErrorException;
20
use Exception;
21
use Limoncello\Contracts\Application\ApplicationSettingsInterface as A;
22
use Limoncello\Contracts\Core\SapiInterface;
23
use Limoncello\Contracts\Exceptions\ExceptionHandlerInterface;
24
use Limoncello\Contracts\Settings\SettingsProviderInterface;
25
use Psr\Container\ContainerInterface;
26
use Psr\Log\LoggerInterface;
27
use Throwable;
28
use Whoops\Handler\PrettyPageHandler;
29
use Whoops\Run;
30
use Zend\Diactoros\Response\HtmlResponse;
31
use Zend\Diactoros\Response\TextResponse;
32
33
/**
34
 * @package Limoncello\Application
35
 *
36
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
37
 */
38
class DefaultHandler implements ExceptionHandlerInterface
39
{
40
    /**
41
     * @inheritdoc
42
     */
43
    public function handleException(Exception $exception, SapiInterface $sapi, ContainerInterface $container): void
44
    {
45
        $this->handle($exception, $sapi, $container);
46
    }
47
48
    /**
49
     * @inheritdoc
50
     */
51 1
    public function handleThrowable(Throwable $throwable, SapiInterface $sapi, ContainerInterface $container): void
52
    {
53 1
        $this->handle($throwable, $sapi, $container);
54
    }
55
56
    /**
57
     * @inheritdoc
58
     */
59 1
    public function handleFatal(array $error, ContainerInterface $container): void
60
    {
61 1
        $errorException = new ErrorException($error['message'], $error['type'], 1, $error['file'], $error['line']);
62 1
        $this->logException($errorException, $container, 'Fatal error');
63
    }
64
65
    /**
66
     * @param Throwable           $exception
67
     * @param SapiInterface       $sapi
68
     * @param ContainerInterface  $container
69
     *
70
     * @return void
71
     *
72
     * @SuppressWarnings(PHPMD.ElseExpression)
73
     */
74 1
    private function handle(Throwable $exception, SapiInterface $sapi, ContainerInterface $container): void
75
    {
76 1
        $message  = 'Internal Server Error';
77
78 1
        $this->logException($exception, $container, $message);
79
80 1
        list($isDebug, $appName, $exceptionDumper) = $this->getSettings($container);
81
82 1
        if ($isDebug === true) {
83
            $run = new Run();
84
85
            // If these two options are not used it would work fine with PHP Unit and XDebug,
86
            // however it produces output to console under PhpDbg. So we need a couple of
87
            // tweaks to make it work predictably in both environments.
88
            //
89
            // this one forbids Whoops spilling output to console
90
            $run->writeToOutput(false);
91
            // by default after sending error to output Whoops stops execution
92
            // as we want just generated output `string` we instruct not to halt
93
            $run->allowQuit(false);
94
95
            $handler = new PrettyPageHandler();
96
97
            if ($exceptionDumper !== null) {
98
                $appSpecificDetails = call_user_func($exceptionDumper, $exception, $container);
99
                $handler->addDataTable("$appName Details", $appSpecificDetails);
100
            }
101
102
            $handler->setPageTitle("Whoops! There was a problem with '$appName'.");
103
            $run->pushHandler($handler);
0 ignored issues
show
Documentation introduced by
$handler is of type object<Whoops\Handler\PrettyPageHandler>, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
104
105
            $htmlMessage = $run->handleException($exception);
106
            $response    = new HtmlResponse($htmlMessage, 500);
0 ignored issues
show
Security Bug introduced by
It seems like $htmlMessage defined by $run->handleException($exception) on line 105 can also be of type false; however, Zend\Diactoros\Response\...Response::__construct() does only seem to accept string|object<Psr\Http\Message\StreamInterface>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
107
        } else {
108 1
            $response = new TextResponse($message, 500);
109
        }
110
111 1
        $sapi->handleResponse($response);
112
    }
113
114
    /**
115
     * @param ContainerInterface $container
116
     *
117
     * @return array
118
     */
119 1
    private function getSettings(ContainerInterface $container): array
120
    {
121
        /** @var SettingsProviderInterface $settingsProvider */
122 1
        if ($container->has(SettingsProviderInterface::class) === true &&
123 1
            ($settingsProvider = $container->get(SettingsProviderInterface::class)) !== null &&
124 1
            $settingsProvider->has(A::class) === true
125
        ) {
126
            $appSettings = $settingsProvider->get(A::class);
127
128
            return [
129
                $appSettings[A::KEY_IS_DEBUG],
130
                $appSettings[A::KEY_APP_NAME],
131
                $appSettings[A::KEY_EXCEPTION_DUMPER] ?? null,
132
            ];
133
        }
134
135 1
        return [false, null, null];
136
    }
137
138
    /**
139
     * @param Throwable          $exception
140
     * @param ContainerInterface $container
141
     * @param string             $message
142
     *
143
     * @return void
144
     */
145 1
    private function logException(Throwable $exception, ContainerInterface $container, string $message): void
146
    {
147 1
        if ($container->has(LoggerInterface::class) === true) {
148
            /** @var LoggerInterface $logger */
149
            $logger = $container->get(LoggerInterface::class);
150
            $logger->critical($message, ['exception' => $exception]);
151
        }
152
    }
153
}
154