Completed
Push — master ( 32e652...8e829c )
by Neomerx
11:56
created

createThrowableTextResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0
cc 1
eloc 10
nc 1
nop 3

1 Method

Rating   Name   Duplication   Size   Complexity  
A WhoopsThrowableHandler.php$0 ➔ __construct() 0 5 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);
0 ignored issues
show
Security Bug introduced by
It seems like $html defined by $run->handleException($throwable) on line 81 can also be of type false; however, Limoncello\Application\E...ThrowableHtmlResponse() does only seem to accept string, 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...
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