Passed
Push — dev ( 563714...88b039 )
by Marcin
02:37
created

ExceptionHandlerHelper::error()   F

Complexity

Conditions 13
Paths 864

Size

Total Lines 101
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 42
CRAP Score 13.4931

Importance

Changes 14
Bugs 0 Features 0
Metric Value
cc 13
eloc 51
c 14
b 0
f 0
nc 864
nop 4
dl 0
loc 101
ccs 42
cts 49
cp 0.8571
crap 13.4931
rs 2.6387

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
namespace MarcinOrlowski\ResponseBuilder;
5
6
/**
7
 * Exception handler using ResponseBuilder to return JSON even in such hard tines
8
 *
9
 * @package   MarcinOrlowski\ResponseBuilder
10
 *
11
 * @author    Marcin Orlowski <mail (#) marcinorlowski (.) com>
12
 * @copyright 2016-2019 Marcin Orlowski
13
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
14
 * @link      https://github.com/MarcinOrlowski/laravel-api-response-builder
15
 */
16
17
use Exception;
18
use Illuminate\Auth\AuthenticationException;
19
use Illuminate\Support\Facades\Config;
20
use Illuminate\Support\Facades\Lang;
21
use Illuminate\Validation\ValidationException;
22
use Symfony\Component\HttpFoundation\Response as HttpResponse;
23
use Symfony\Component\HttpKernel\Exception\HttpException;
24
25
/**
26
 * Class ExceptionHandlerHelper
27
 */
28
class ExceptionHandlerHelper
29
{
30
	/**
31
	 * Exception types
32
	 */
33
	public const TYPE_HTTP_NOT_FOUND_KEY           = 'http_not_found';
34
	public const TYPE_HTTP_SERVICE_UNAVAILABLE_KEY = 'http_service_unavailable';
35
	public const TYPE_HTTP_UNAUTHORIZED_KEY        = 'authentication_exception';
36
	public const TYPE_HTTP_EXCEPTION_KEY           = 'http_exception';
37
	public const TYPE_VALIDATION_EXCEPTION_KEY     = 'validation_exception';
38
	public const TYPE_UNCAUGHT_EXCEPTION_KEY       = 'uncaught_exception';
39
	public const TYPE_AUTHENTICATION_EXCEPTION_KEY = 'authentication_exception';
40
41
	/**
42
	 * Render an exception into valid API response.
43
	 *
44
	 * @param \Illuminate\Http\Request $request   Request object
45
	 * @param \Exception               $exception Exception
46
	 *
47
	 * @return HttpResponse
48
	 */
49 2
	public static function render(/** @scrutinizer ignore-unused */ $request, Exception $exception): HttpResponse
50
	{
51 2
		$result = null;
52
53 2
		if ($exception instanceof HttpException) {
54 1
			switch ($exception->getStatusCode()) {
55 1
				case HttpResponse::HTTP_NOT_FOUND:
56 1
					$result = static::error($exception, static::TYPE_HTTP_NOT_FOUND_KEY,
57 1
						BaseApiCodes::EX_HTTP_NOT_FOUND(), HttpResponse::HTTP_NOT_FOUND);
58 1
					break;
59
60 1
				case HttpResponse::HTTP_SERVICE_UNAVAILABLE:
61 1
					$result = static::error($exception, static::TYPE_HTTP_SERVICE_UNAVAILABLE_KEY,
62 1
						BaseApiCodes::EX_HTTP_SERVICE_UNAVAILABLE(), HttpResponse::HTTP_SERVICE_UNAVAILABLE);
63 1
					break;
64
65 1
				case HttpResponse::HTTP_UNAUTHORIZED:
66 1
					$result = static::error($exception, static::TYPE_HTTP_UNAUTHORIZED_KEY,
67 1
						BaseApiCodes::EX_AUTHENTICATION_EXCEPTION(), HttpResponse::HTTP_UNAUTHORIZED);
68 1
					break;
69
70
				default:
71 1
					$result = static::error($exception, static::TYPE_HTTP_EXCEPTION_KEY,
72 1
						BaseApiCodes::EX_HTTP_EXCEPTION(), HttpResponse::HTTP_BAD_REQUEST);
73 1
					break;
74
			}
75 2
		} elseif ($exception instanceof ValidationException) {
76 1
			$result = static::error($exception, static::TYPE_VALIDATION_EXCEPTION_KEY,
77 1
				BaseApiCodes::EX_VALIDATION_EXCEPTION(), HttpResponse::HTTP_BAD_REQUEST);
78
		}
79
80 2
		if ($result === null) {
81 2
			$result = static::error($exception, static::TYPE_UNCAUGHT_EXCEPTION_KEY,
82 2
				BaseApiCodes::EX_UNCAUGHT_EXCEPTION(), HttpResponse::HTTP_INTERNAL_SERVER_ERROR);
83
		}
84
85 2
		return $result;
86
	}
87
88
	/**
89
	 * Convert an authentication exception into an unauthenticated response.
90
	 *
91
	 * @param \Illuminate\Http\Request                 $request
92
	 * @param \Illuminate\Auth\AuthenticationException $exception
93
	 *
94
	 * @return HttpResponse
95
	 */
96 1
	protected function unauthenticated(/** @scrutinizer ignore-unused */ $request, AuthenticationException $exception): HttpResponse
97
	{
98 1
		return static::error($exception, 'authentication_exception', BaseApiCodes::EX_AUTHENTICATION_EXCEPTION());
99
	}
100
101
	/**
102
	 * Process singe error and produce valid API response
103
	 *
104
	 * @param Exception $exception            Exception to be processed
105
	 * @param string    $exception_config_key Category of the exception
106
	 * @param integer   $fallback_api_code    API code to return
107
	 * @param integer   $fallback_http_code   HTTP code to return
108
	 *
109
	 * @return HttpResponse
110
	 */
111 5
	protected static function error(Exception $exception, $exception_config_key, $fallback_api_code,
112
	                                $fallback_http_code = ResponseBuilder::DEFAULT_HTTP_CODE_ERROR): HttpResponse
113
	{
114
		// common prefix for config key
115 5
		$base_config_key = sprintf('%s.exception', ResponseBuilder::CONF_EXCEPTION_HANDLER_KEY);
116
117
		// get API and HTTP codes from exception handler config or use fallback values if no such
118
		// config fields are set.
119 5
		$api_code = Config::get("{$base_config_key}.{$exception_config_key}.code", $fallback_api_code);
120 5
		$http_code = Config::get("{$base_config_key}.{$exception_config_key}.http_code", $fallback_http_code);
121
122
		// check if we now have valid HTTP error code for this case or need to make one up.
123 5
		if ($http_code < ResponseBuilder::ERROR_HTTP_CODE_MIN) {
124
			// no code, let's try to get the exception status
125 2
			$http_code = ($exception instanceof \Symfony\Component\HttpKernel\Exception\HttpException)
126 2
				? $exception->getStatusCode()
127 2
				: $exception->getCode();
128
		}
129
130
		// can it be considered valid HTTP error code?
131 5
		if ($http_code < ResponseBuilder::ERROR_HTTP_CODE_MIN) {
132 1
			$http_code = $fallback_http_code;
133
		}
134
135
		// let's figure out what event we are handling now
136
		$known_codes = [
137 5
			self::TYPE_HTTP_NOT_FOUND_KEY           => BaseApiCodes::EX_HTTP_NOT_FOUND(),
138 5
			self::TYPE_HTTP_SERVICE_UNAVAILABLE_KEY => BaseApiCodes::EX_HTTP_SERVICE_UNAVAILABLE(),
139 5
			self::TYPE_UNCAUGHT_EXCEPTION_KEY       => BaseApiCodes::EX_UNCAUGHT_EXCEPTION(),
140 5
			self::TYPE_AUTHENTICATION_EXCEPTION_KEY => BaseApiCodes::EX_AUTHENTICATION_EXCEPTION(),
141 5
			self::TYPE_VALIDATION_EXCEPTION_KEY     => BaseApiCodes::EX_VALIDATION_EXCEPTION(),
142 5
			self::TYPE_HTTP_EXCEPTION_KEY           => BaseApiCodes::EX_HTTP_EXCEPTION(),
143
		];
144 5
		$base_api_code = BaseApiCodes::NO_ERROR_MESSAGE();
145 5
		foreach ($known_codes as $item_config_key => $item_api_code) {
146 5
			if ($api_code === Config::get("{$base_config_key}.{$item_config_key}.code", $item_api_code)) {
147 5
				$base_api_code = $api_code;
148 5
				break;
149
			}
150
		}
151
152
		/** @var array|null $data Optional payload to return */
153 5
		$data = null;
154 5
		if ($api_code === Config::get("{$base_config_key}.validation_exception.code", BaseApiCodes::EX_VALIDATION_EXCEPTION())) {
155
			/** @var ValidationException $exception */
156 1
			$data = [ResponseBuilder::KEY_MESSAGES => $exception->validator->errors()->messages()];
157
		}
158
159 5
		$key = BaseApiCodes::getCodeMessageKey($api_code) ?? BaseApiCodes::getCodeMessageKey($base_api_code);
160
161
		// let's build error error_message
162 5
		$ex_message = trim($exception->getMessage());
163
164
		// ensure we won't fail due to exception incorect encoding
165
		// FIXME: Is this block really needed?
166 5
		if (!mb_check_encoding($ex_message, 'UTF-8')) {
167
			// let's check there's iconv and mb_string available
168
			if (function_exists('iconv') && function_exists('mb_detec_encoding')) {
169
				$ex_message = iconv(mb_detect_encoding($ex_message, mb_detect_order(), true), 'UTF-8', $ex_message);
170
			} else {
171
				// lame fallback, in case there's no iconv/mb_string installed
172
				$ex_message = htmlspecialchars_decode(htmlspecialchars($ex_message, ENT_SUBSTITUTE, 'UTF-8'));
173
			}
174
		}
175
176
		/** @var string $error_message final error error_message */
177 5
		$error_message = $ex_message;
178 5
		$ex_message = get_class($exception);
179
180 5
		if ($ex_message === '') {
181
			$error_message = Lang::get($key, [
182
				'api_code'      => $api_code,
183
				'error_message' => $ex_message,
184
				'message'       => get_class($exception),
185
			]);
186
		}
187
188
		// if we do not have any error_message in the hand yet, we need to fall back to built-in string configured
189
		// for this particular exception.
190 5
		if ($error_message === '') {
191 4
			$error_message = Lang::get($key, [
192 4
				'api_code'      => $api_code,
193 4
				'error_message' => $ex_message,
194 4
				'message'       => get_class($exception),
195
			]);
196
		}
197
198
		// if we have trace data debugging enabled, let's gather some debug
199
		// info and add to the response.
200 5
		$trace_data = null;
201 5
		if (Config::get(ResponseBuilder::CONF_KEY_DEBUG_EX_TRACE_ENABLED, false)) {
202
			$trace_data = [
203 1
				Config::get(ResponseBuilder::CONF_KEY_DEBUG_EX_TRACE_KEY, ResponseBuilder::KEY_TRACE) => [
204 1
					ResponseBuilder::KEY_CLASS => get_class($exception),
205 1
					ResponseBuilder::KEY_FILE  => $exception->getFile(),
206 1
					ResponseBuilder::KEY_LINE  => $exception->getLine(),
207
				],
208
			];
209
		}
210
211 5
		return ResponseBuilder::errorWithMessageAndDataAndDebug($api_code, $error_message, $data, $http_code, null, $trace_data);
212
	}
213
214
}
215