Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Completed
Pull Request — master (#264)
by Jérémiah
12:16
created

ErrorHandler::flattenErrors()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 19
ccs 10
cts 10
cp 1
rs 9.2
cc 4
eloc 11
nc 3
nop 1
crap 4
1
<?php
2
3
namespace Overblog\GraphQLBundle\Error;
4
5
use GraphQL\Error\Error as GraphQLError;
6
use GraphQL\Error\FormattedError;
7
use GraphQL\Error\UserError as GraphQLUserError;
8
use GraphQL\Executor\ExecutionResult;
9
use Psr\Log\LoggerInterface;
10
use Psr\Log\LogLevel;
11
use Psr\Log\NullLogger;
12
13
class ErrorHandler
14
{
15
    const DEFAULT_ERROR_MESSAGE = 'Internal server Error';
16
    const DEFAULT_USER_WARNING_CLASS = UserWarning::class;
17
    const DEFAULT_USER_ERROR_CLASS = UserError::class;
18
    /** callable */
19
    const DEFAULT_ERROR_FORMATTER = [FormattedError::class, 'createFromException'];
20
21
    /** @var LoggerInterface */
22
    private $logger;
23
24
    /** @var string */
25
    private $internalErrorMessage;
26
27
    /** @var array */
28
    private $exceptionMap;
29
30
    /** @var string */
31
    private $userWarningClass = self::DEFAULT_USER_WARNING_CLASS;
32
33
    /** @var string */
34
    private $userErrorClass = self::DEFAULT_USER_ERROR_CLASS;
35
36
    /** @var callable|null */
37
    private $errorFormatter;
38
39
    /** @var bool */
40
    private $mapExceptionsToParent;
41
42 74
    public function __construct(
43
        $internalErrorMessage = null,
44
        LoggerInterface $logger = null,
45
        array $exceptionMap = [],
46
        $mapExceptionsToParent = false
47
    ) {
48 74
        $this->logger = (null === $logger) ? new NullLogger() : $logger;
49 74
        if (empty($internalErrorMessage)) {
50 15
            $internalErrorMessage = self::DEFAULT_ERROR_MESSAGE;
51
        }
52 74
        $this->internalErrorMessage = $internalErrorMessage;
53 74
        $this->exceptionMap = $exceptionMap;
54 74
        $this->mapExceptionsToParent = $mapExceptionsToParent;
55 74
    }
56
57 59
    public function setUserWarningClass($userWarningClass)
58
    {
59 59
        $this->userWarningClass = $userWarningClass;
60
61 59
        return $this;
62
    }
63
64 59
    public function setUserErrorClass($userErrorClass)
65
    {
66 59
        $this->userErrorClass = $userErrorClass;
67
68 59
        return $this;
69
    }
70
71 60
    public function setErrorFormatter(callable $errorFormatter = null)
72
    {
73 60
        $this->errorFormatter = $errorFormatter;
74
75 60
        return $this;
76
    }
77
78
    /**
79
     * @param \Exception|\Error $exception
80
     * @param string            $errorLevel
81
     */
82 10
    public function logException($exception, $errorLevel = LogLevel::ERROR)
83
    {
84 10
        $message = sprintf(
85 10
            '%s: %s[%d] (caught exception) at %s line %s.',
86 10
            get_class($exception),
87 10
            $exception->getMessage(),
88 10
            $exception->getCode(),
89 10
            $exception->getFile(),
90 10
            $exception->getLine()
91
        );
92
93 10
        $this->logger->$errorLevel($message, ['exception' => $exception]);
94 10
    }
95
96 73
    public function handleErrors(ExecutionResult $executionResult, $throwRawException = false)
97
    {
98 73
        $errorFormatter = $this->errorFormatter ? $this->errorFormatter : self::DEFAULT_ERROR_FORMATTER;
99 73
        $executionResult->setErrorFormatter($errorFormatter);
100 73
        FormattedError::setInternalErrorMessage($this->internalErrorMessage);
101 73
        $exceptions = $this->treatExceptions($executionResult->errors, $throwRawException);
102 70
        $executionResult->errors = $exceptions['errors'];
103 70
        if (!empty($exceptions['extensions']['warnings'])) {
104 7
            $executionResult->extensions['warnings'] = array_map($errorFormatter, $exceptions['extensions']['warnings']);
105
        }
106 70
    }
107
108
    /**
109
     * @param GraphQLError[] $errors
110
     * @param bool           $throwRawException
111
     *
112
     * @return array
113
     *
114
     * @throws \Error|\Exception
115
     */
116 73
    private function treatExceptions(array $errors, $throwRawException)
117
    {
118
        $treatedExceptions = [
119 73
            'errors' => [],
120
            'extensions' => [
121
                'warnings' => [],
122
            ],
123
        ];
124
125
        /** @var GraphQLError $error */
126 73
        foreach ($this->flattenErrors($errors) as $error) {
127 26
            $rawException = $this->convertException($error->getPrevious());
128
129
            // raw GraphQL Error or InvariantViolation exception
130 26
            if (null === $rawException || $rawException instanceof GraphQLUserError) {
131 9
                $treatedExceptions['errors'][] = $error;
132 9
                continue;
133
            }
134
135
            // recreate a error with converted exception
136 18
            $errorWithConvertedException = new GraphQLError(
137 18
                $error->getMessage(),
138 18
                $error->nodes,
139 18
                $error->getSource(),
140 18
                $error->getPositions(),
141 18
                $error->path,
142 18
                $rawException
143
            );
144
145
            // user error
146 18
            if ($rawException instanceof $this->userErrorClass) {
147 8
                $treatedExceptions['errors'][] = $errorWithConvertedException;
148 8
                if ($rawException->getPrevious()) {
149 5
                    $this->logException($rawException->getPrevious());
150
                }
151 8
                continue;
152
            }
153
154
            // user warning
155 11
            if ($rawException instanceof $this->userWarningClass) {
156 7
                $treatedExceptions['extensions']['warnings'][] = $errorWithConvertedException;
157 7
                if ($rawException->getPrevious()) {
158 3
                    $this->logException($rawException->getPrevious(), LogLevel::WARNING);
159
                }
160 7
                continue;
161
            }
162
163
            // if is a catch exception wrapped in Error
164 5
            if ($throwRawException) {
165 3
                throw $rawException;
166
            }
167
168 2
            $this->logException($rawException, LogLevel::CRITICAL);
169
170 2
            $treatedExceptions['errors'][] = $errorWithConvertedException;
171
        }
172
173 70
        return $treatedExceptions;
174
    }
175
176
    /**
177
     * @param GraphQLError[] $errors
178
     *
179
     * @return GraphQLError[]
180
     */
181 73
    private function flattenErrors(array $errors)
182
    {
183 73
        $flattenErrors = [];
184
185 73
        foreach ($errors as $error) {
186 26
            $rawException = $error->getPrevious();
187
            // multiple errors
188 26
            if ($rawException instanceof UserErrors) {
189 1
                $rawExceptions = $rawException;
190 1
                foreach ($rawExceptions->getErrors() as $rawException) {
191 1
                    $flattenErrors[] = GraphQLError::createLocatedError($rawException, $error->nodes, $error->path);
192
                }
193
            } else {
194 26
                $flattenErrors[] = $error;
195
            }
196
        }
197
198 73
        return $flattenErrors;
199
    }
200
201
    /**
202
     * Tries to convert a raw exception into a user warning or error
203
     * that is displayed to the user.
204
     *
205
     * @param \Exception|\Error $rawException
206
     *
207
     * @return \Exception|\Error
208
     */
209 26
    private function convertException($rawException = null)
210
    {
211 26
        if (null === $rawException) {
212 9
            return;
213
        }
214
215 18
        $errorClass = $this->findErrorClass($rawException);
216 18
        if (null !== $errorClass) {
217 8
            return new $errorClass($rawException->getMessage(), $rawException->getCode(), $rawException);
218
        }
219
220 10
        return $rawException;
221
    }
222
223
    /**
224
     * @param \Exception|\Error $rawException
225
     *
226
     * @return string|null
227
     */
228 18
    private function findErrorClass($rawException)
229
    {
230 18
        $rawExceptionClass = get_class($rawException);
231 18
        if (isset($this->exceptionMap[$rawExceptionClass])) {
232 7
            return $this->exceptionMap[$rawExceptionClass];
233
        }
234
235 11
        if ($this->mapExceptionsToParent) {
236 2
            return $this->findErrorClassUsingParentException($rawException);
237
        }
238
239 9
        return null;
240
    }
241
242
    /**
243
     * @param \Exception|\Error $rawException
244
     *
245
     * @return string|null
246
     */
247 2
    private function findErrorClassUsingParentException($rawException)
248
    {
249 2
        foreach ($this->exceptionMap as $rawExceptionClass => $errorClass) {
250 1
            if ($rawException instanceof $rawExceptionClass) {
251 1
                return $errorClass;
252
            }
253
        }
254
255 1
        return null;
256
    }
257
}
258