dler.php$0   A
last analyzed

Complexity

Total Complexity 2

Size/Duplication

Total Lines 26
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 2
lcom 0
cbo 1
dl 0
loc 26
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
1
<?php declare (strict_types = 1);
2
3
namespace Limoncello\Flute\Http\ThrowableHandlers;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Exception;
22
use Limoncello\Common\Reflection\ClassIsTrait;
23
use Limoncello\Contracts\Exceptions\ThrowableHandlerInterface;
24
use Limoncello\Contracts\Http\ThrowableResponseInterface;
25
use Limoncello\Flute\Contracts\Encoder\EncoderInterface;
26
use Limoncello\Flute\Contracts\Exceptions\JsonApiThrowableConverterInterface as ConverterInterface;
27
use Limoncello\Flute\Http\JsonApiResponse;
28
use Neomerx\JsonApi\Exceptions\JsonApiException;
29
use Neomerx\JsonApi\Schema\Error;
30
use Neomerx\JsonApi\Schema\ErrorCollection;
31
use Psr\Container\ContainerInterface;
32
use Psr\Log\LoggerAwareTrait;
33
use Throwable;
34
use function array_key_exists;
35
use function assert;
36
use function get_class;
37
38
/**
39
 * @package Limoncello\Flute
40
 *
41
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
42
 */
43
class FluteThrowableHandler implements ThrowableHandlerInterface
44
{
45
    use LoggerAwareTrait, ClassIsTrait;
46
47
    /**
48
     * Those classes will not be logged. Note that classes are expected to be keys but not values.
49
     *
50
     * @var array
51
     */
52
    private $doNotLogClassesAsKeys;
53
54
    /**
55
     * @var int
56
     */
57
    private $httpCodeForUnexpected;
58
59
    /**
60
     * @var bool
61
     */
62
    private $isDebug;
63
64
    /**
65
     * @var EncoderInterface
66
     */
67
    private $encoder;
68
69
    /**
70
     * @var string|null
71
     */
72
    private $throwableConverter;
73
74
    /**
75
     * @param EncoderInterface $encoder
76
     * @param array            $noLogClassesAsKeys
77 6
     * @param int              $codeForUnexpected
78
     * @param bool             $isDebug
79
     * @param null|string      $converterClass
80
     */
81
    public function __construct(
82
        EncoderInterface $encoder,
83
        array $noLogClassesAsKeys,
84 6
        int $codeForUnexpected,
85 6
        bool $isDebug,
86 6
        ?string $converterClass
87
    ) {
88
        assert(
89 6
            $converterClass === null ||
90 6
            static::classImplements($converterClass, ConverterInterface::class)
91 6
        );
92 6
93 6
        $this->doNotLogClassesAsKeys = $noLogClassesAsKeys;
94
        $this->httpCodeForUnexpected = $codeForUnexpected;
95
        $this->isDebug               = $isDebug;
96
        $this->encoder               = $encoder;
97
        $this->throwableConverter    = $converterClass;
98
    }
99
100
    /**
101 5
     * @inheritdoc
102
     *
103 5
     * @SuppressWarnings(PHPMD.ElseExpression)
104
     */
105 5
    public function createResponse(Throwable $throwable, ContainerInterface $container): ThrowableResponseInterface
106 5
    {
107
        unset($container);
108 5
109
        $message            = 'Internal Server Error';
110
        $isJsonApiException = $throwable instanceof JsonApiException;
111 5
112
        $this->logError($throwable, $message);
113
114 2
        // if exception converter is specified it will be used to convert throwable to JsonApiException
115 2
        if ($isJsonApiException === false && $this->throwableConverter !== null) {
116 1
            try {
117 1
                /** @var ConverterInterface $converterClass */
118 1
                $converterClass = $this->throwableConverter;
119
                if (($converted = $converterClass::convert($throwable)) !== null) {
120 1
                    assert($converted instanceof JsonApiException);
121
                    $throwable          = $converted;
122
                    $isJsonApiException = true;
123
                }
124
            } catch (Throwable $ignored) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
125 5
            }
126
        }
127 2
128 2
        // compose JSON API Error with appropriate level of details
129
        if ($isJsonApiException === true) {
130 3
            /** @var JsonApiException $throwable */
131 3
            $errors   = $throwable->getErrors();
132 3
            $httpCode = $throwable->getHttpCode();
133 3
        } else {
134 3
            $errors   = new ErrorCollection();
135 3
            $httpCode = $this->getHttpCodeForUnexpectedThrowable();
136
            $details  = null;
137 3
            if ($this->isDebug === true) {
138
                $message = $throwable->getMessage();
139
                $details = (string)$throwable;
140
            }
141 5
            $errors->add(new Error(null, null, null, (string)$httpCode, null, $message, $details));
142
        }
143 5
144
        // encode the error and send to client
145
        $content = $this->encoder->encodeErrors($errors);
0 ignored issues
show
Documentation introduced by
$errors is of type object<Neomerx\JsonApi\Schema\ErrorCollection>, but the function expects a object<Neomerx\JsonApi\C...racts\Encoder\iterable>.

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...
146
147
        return $this->createThrowableJsonApiResponse($throwable, $content, $httpCode);
148
    }
149
150
    /**
151
     * @param Throwable $throwable
152 5
     * @param string    $message
153
     *
154 5
     * @return void
155
     */
156
    private function logError(Throwable $throwable, string $message): void
157 5
    {
158 1
        if ($this->logger !== null && $this->shouldBeLogged($throwable) === true) {
159
            // on error (e.g. no permission to write on disk or etc) ignore
160
            try {
161
                $this->logger->error($message, ['error' => $throwable]);
162
            } catch (Exception $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
163
            }
164
        }
165
    }
166 3
167
    /**
168 3
     * @return int
169
     */
170
    private function getHttpCodeForUnexpectedThrowable(): int
171
    {
172
        return $this->httpCodeForUnexpected;
173
    }
174
175
    /**
176 5
     * @param Throwable $throwable
177
     *
178 5
     * @return bool
179
     */
180 5
    private function shouldBeLogged(Throwable $throwable): bool
181
    {
182
        $result = array_key_exists(get_class($throwable), $this->doNotLogClassesAsKeys) === false;
183
184
        return $result;
185
    }
186
187
    /**
188
     * @param Throwable $throwable
189
     * @param string    $content
190
     * @param int       $status
191
     *
192
     * @return ThrowableResponseInterface
193
     */
194
    private function createThrowableJsonApiResponse(
195
        Throwable $throwable,
196
        string $content,
197
        int $status
198
    ): ThrowableResponseInterface {
199
        return new class ($throwable, $content, $status) extends JsonApiResponse implements ThrowableResponseInterface
200
        {
201
            /**
202
             * @var Throwable
203
             */
204
            private $throwable;
205
206
            /**
207 5
             * @param Throwable $throwable
208
             * @param string    $content
209 5
             * @param int       $status
210 5
             */
211
            public function __construct(Throwable $throwable, string $content, int $status)
212
            {
213
                parent::__construct($content, $status);
214
                $this->throwable = $throwable;
215
            }
216 5
217
            /**
218 5
             * @return Throwable
219
             */
220
            public function getThrowable(): Throwable
221
            {
222
                return $this->throwable;
223
            }
224
        };
225
    }
226
}
227