Completed
Push — develop ( 32e652...cf44cf )
by Neomerx
01:41
created

WhoopsThrowableHandler   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 157
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 2
dl 0
loc 157
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A hp$0 ➔ __construct() 0 5 1
A hp$1 ➔ __construct() 0 5 1
B createResponse() 0 41 3
A getSettings() 0 18 4
A logException() 0 15 3
A createThrowableTextResponse() 0 21 1
A createThrowableHtmlResponse() 0 21 1
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 Exception;
20
use Limoncello\Contracts\Application\ApplicationSettingsInterface as A;
21
use Limoncello\Contracts\Exceptions\ThrowableHandlerInterface;
22
use Limoncello\Contracts\Http\ThrowableResponseInterface;
23
use Limoncello\Contracts\Settings\SettingsProviderInterface;
24
use Limoncello\Core\Application\ThrowableResponseTrait;
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 WhoopsThrowableHandler implements ThrowableHandlerInterface
39
{
40
    /** Default HTTP code. */
41
    protected const DEFAULT_HTTP_ERROR_CODE = 500;
42
43
    /**
44
     * @inheritdoc
45
     *
46
     * @SuppressWarnings(PHPMD.ElseExpression)
47
     */
48
    public function createResponse(Throwable $throwable, ContainerInterface $container): ThrowableResponseInterface
49
    {
50
        $message = 'Internal Server Error';
51
52
        $this->logException($throwable, $container, $message);
53
54
        list($isDebug, $appName, $exceptionDumper) = $this->getSettings($container);
55
56
        if ($isDebug === true) {
57
            $run = new Run();
58
59
            // If these two options are not used it would work fine with PHP Unit and XDebug,
60
            // however it produces output to console under PhpDbg. So we need a couple of
61
            // tweaks to make it work predictably in both environments.
62
            //
63
            // this one forbids Whoops spilling output to console
64
            $run->writeToOutput(false);
65
            // by default after sending error to output Whoops stops execution
66
            // as we want just generated output `string` we instruct not to halt
67
            $run->allowQuit(false);
68
69
            $handler = new PrettyPageHandler();
70
            // without the line below Whoops is too smart and do not produce any output in tests
71
            $handler->handleUnconditionally(true);
72
73
            if ($exceptionDumper !== null) {
74
                $appSpecificDetails = call_user_func($exceptionDumper, $throwable, $container);
75
                $handler->addDataTable("$appName Details", $appSpecificDetails);
76
            }
77
78
            $handler->setPageTitle("Whoops! There was a problem with '$appName'.");
79
            $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...
80
81
            $html     = $run->handleException($throwable);
82
            $response = $this->createThrowableHtmlResponse($throwable, $html, static::DEFAULT_HTTP_ERROR_CODE);
83
        } else {
84
            $response = $this->createThrowableTextResponse($throwable, $message, static::DEFAULT_HTTP_ERROR_CODE);
85
        }
86
87
        return $response;
88
    }
89
90
    /**
91
     * @param ContainerInterface $container
92
     *
93
     * @return array
94
     */
95
    private function getSettings(ContainerInterface $container): array
96
    {
97
        $appSettings = null;
98
99
        /** @var SettingsProviderInterface $settingsProvider */
100
        if ($container->has(SettingsProviderInterface::class) === true &&
101
            ($settingsProvider = $container->get(SettingsProviderInterface::class)) !== null &&
102
            $settingsProvider->has(A::class) === true
103
        ) {
104
            $appSettings = $settingsProvider->get(A::class);
105
        }
106
107
        return [
108
            $appSettings[A::KEY_IS_DEBUG] ?? false,
109
            $appSettings[A::KEY_APP_NAME] ?? null,
110
            $appSettings[A::KEY_EXCEPTION_DUMPER] ?? null,
111
        ];
112
    }
113
114
    /**
115
     * @param Throwable          $exception
116
     * @param ContainerInterface $container
117
     * @param string             $message
118
     *
119
     * @return void
120
     */
121
    private function logException(Throwable $exception, ContainerInterface $container, string $message): void
122
    {
123
        if ($container->has(LoggerInterface::class) === true) {
124
            /** @var LoggerInterface $logger */
125
            $logger = $container->get(LoggerInterface::class);
126
127
            // The sad truth is that when you have a problem logging might not be available (e.g. no permissions
128
            // to write on a disk). We can't do much with it and can only hope that the error information will be
129
            // delivered to the user other way.
130
            try {
131
                $logger->critical($message, ['exception' => $exception]);
132
            } catch (Exception $secondException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
133
            }
134
        }
135
    }
136
137
    /**
138
     * @param Throwable $throwable
139
     * @param string    $text
140
     * @param int       $status
141
     *
142
     * @return ThrowableResponseInterface
143
     */
144
    private function createThrowableTextResponse(
145
        Throwable $throwable,
146
        string $text,
147
        int $status
148
    ): ThrowableResponseInterface {
149
        return new class ($throwable, $text, $status) extends TextResponse implements ThrowableResponseInterface
150
        {
151
            use ThrowableResponseTrait;
152
153
            /**
154
             * @param Throwable $throwable
155
             * @param string    $text
156
             * @param int       $status
157
             */
158
            public function __construct(Throwable $throwable, string $text, int $status)
159
            {
160
                parent::__construct($text, $status);
161
                $this->setThrowable($throwable);
162
            }
163
        };
164
    }
165
166
    /**
167
     * @param Throwable $throwable
168
     * @param string    $text
169
     * @param int       $status
170
     *
171
     * @return ThrowableResponseInterface
172
     */
173
    private function createThrowableHtmlResponse(
174
        Throwable $throwable,
175
        string $text,
176
        int $status
177
    ): ThrowableResponseInterface {
178
        return new class ($throwable, $text, $status) extends HtmlResponse implements ThrowableResponseInterface
179
        {
180
            use ThrowableResponseTrait;
181
182
            /**
183
             * @param Throwable $throwable
184
             * @param string    $text
185
             * @param int       $status
186
             */
187
            public function __construct(Throwable $throwable, string $text, int $status)
188
            {
189
                parent::__construct($text, $status);
190
                $this->setThrowable($throwable);
191
            }
192
        };
193
    }
194
}
195