Completed
Push — master ( 79dcd5...ac1fa0 )
by Neomerx
02:10
created

createThrowableTextResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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

1 Method

Rating   Name   Duplication   Size   Complexity  
A WhoopsThrowableTextHandler.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\ApplicationConfigurationInterface as A;
21
use Limoncello\Contracts\Application\CacheSettingsProviderInterface;
22
use Limoncello\Contracts\Exceptions\ThrowableHandlerInterface;
23
use Limoncello\Contracts\Http\ThrowableResponseInterface;
24
use Limoncello\Core\Application\ThrowableResponseTrait;
25
use Psr\Container\ContainerExceptionInterface;
26
use Psr\Container\ContainerInterface;
27
use Psr\Container\NotFoundExceptionInterface;
28
use Psr\Log\LoggerInterface;
29
use Throwable;
30
use Whoops\Handler\PlainTextHandler;
31
use Whoops\Run;
32
use Zend\Diactoros\Response\TextResponse;
33
34
/**
35
 * @package Limoncello\Application
36
 *
37
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
38
 */
39
class WhoopsThrowableTextHandler implements ThrowableHandlerInterface
40
{
41
    /** Default HTTP code. */
42
    protected const DEFAULT_HTTP_ERROR_CODE = 500;
43
44
    /**
45
     * @inheritdoc
46
     *
47
     * @SuppressWarnings(PHPMD.ElseExpression)
48
     */
49 3
    public function createResponse(Throwable $throwable, ContainerInterface $container): ThrowableResponseInterface
50
    {
51 3
        $message = 'Internal Server Error';
52
53 3
        $this->logException($throwable, $container, $message);
54
55 3
        $isDebug = $this->isDebug($container);
56 3
        if ($isDebug === true) {
57 2
            $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 2
            $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 2
            $run->allowQuit(false);
68
69 2
            $handler = new PlainTextHandler();
70 2
            $handler->setException($throwable);
71 2
            $run->pushHandler($handler);
0 ignored issues
show
Documentation introduced by
$handler is of type object<Whoops\Handler\PlainTextHandler>, 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...
72
73 2
            $text     = $run->handleException($throwable);
74 2
            $response = $this->createThrowableTextResponse($throwable, $text, static::DEFAULT_HTTP_ERROR_CODE);
0 ignored issues
show
Security Bug introduced by
It seems like $text defined by $run->handleException($throwable) on line 73 can also be of type false; however, Limoncello\Application\E...ThrowableTextResponse() 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...
75
        } else {
76 1
            $response = $this->createThrowableTextResponse($throwable, $message, static::DEFAULT_HTTP_ERROR_CODE);
77
        }
78
79 3
        return $response;
80
    }
81
82
    /**
83
     * @param ContainerInterface $container
84
     *
85
     * @return bool
86
     *
87
     * @throws ContainerExceptionInterface
88
     * @throws NotFoundExceptionInterface
89
     */
90 3
    private function isDebug(ContainerInterface $container): bool
91
    {
92 3
        $appConfig = null;
93
94
        /** @var CacheSettingsProviderInterface $settingsProvider */
95 3
        if ($container->has(CacheSettingsProviderInterface::class) === true &&
96 3
            ($settingsProvider = $container->get(CacheSettingsProviderInterface::class)) !== null
97
        ) {
98 3
            $appConfig = $settingsProvider->getApplicationConfiguration();
99
        }
100
101 3
        return $appConfig[A::KEY_IS_DEBUG] ?? false;
102
    }
103
104
    /**
105
     * @param Throwable          $exception
106
     * @param ContainerInterface $container
107
     * @param string             $message
108
     *
109
     * @return void
110
     *
111
     * @throws ContainerExceptionInterface
112
     * @throws NotFoundExceptionInterface
113
     */
114 3
    private function logException(Throwable $exception, ContainerInterface $container, string $message): void
115
    {
116 3
        if ($container->has(LoggerInterface::class) === true) {
117
            /** @var LoggerInterface $logger */
118 3
            $logger = $container->get(LoggerInterface::class);
119
120
            // The sad truth is that when you have a problem logging might not be available (e.g. no permissions
121
            // to write on a disk). We can't do much with it and can only hope that the error information will be
122
            // delivered to the user other way.
123
            try {
124 3
                $logger->critical($message, ['exception' => $exception]);
125 1
            } catch (Exception $secondException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
126
            }
127
        }
128
    }
129
130
    /**
131
     * @param Throwable $throwable
132
     * @param string    $text
133
     * @param int       $status
134
     *
135
     * @return ThrowableResponseInterface
136
     */
137
    private function createThrowableTextResponse(
138
        Throwable $throwable,
139
        string $text,
140
        int $status
141
    ): ThrowableResponseInterface {
142
        return new class ($throwable, $text, $status) extends TextResponse implements ThrowableResponseInterface
143
        {
144
            use ThrowableResponseTrait;
145
146
            /**
147
             * @param Throwable $throwable
148
             * @param string    $text
149
             * @param int       $status
150
             */
151 3
            public function __construct(Throwable $throwable, string $text, int $status)
152
            {
153 3
                parent::__construct($text, $status);
154 3
                $this->setThrowable($throwable);
155
            }
156
        };
157
    }
158
}
159