diff --git a/src/Config/api-error-handler.php b/src/Config/api-error-handler.php index 87ec02c..bdefcca 100644 --- a/src/Config/api-error-handler.php +++ b/src/Config/api-error-handler.php @@ -1,11 +1,38 @@ "\hamidreza2005\LaravelApiErrorHandler\Exceptions\NotFoundException", - "ErrorException" => "\hamidreza2005\LaravelApiErrorHandler\Exceptions\ServerInternalException", - "Illuminate\Database\QueryException" => "\hamidreza2005\LaravelApiErrorHandler\Exceptions\ServerInternalException", - "Illuminate\Auth\AuthenticationException" => "\hamidreza2005\LaravelApiErrorHandler\Exceptions\AccessDeniedException", - "Symfony\Component\HttpKernel\Exception\HttpException" => "\hamidreza2005\LaravelApiErrorHandler\Exceptions\AccessDeniedException", - "Illuminate\Validation\ValidationException" => "\hamidreza2005\LaravelApiErrorHandler\Exceptions\ValidationException", - "Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException"=>"\hamidreza2005\LaravelApiErrorHandler\Exceptions\NotFoundException", + + /* + * this is where you define which handler deal with which errors. each handler can handle multiple errors + */ + + "handlers" =>[ + NotFoundException::class => [ + "Symfony\Component\HttpKernel\Exception\NotFoundHttpException", + "Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException" + ], + ServerInternalException::class => [ + "ErrorException", + "Illuminate\Database\QueryException" + ], + AccessDeniedException::class => [ + "Illuminate\Auth\AuthenticationException", + "Symfony\Component\HttpKernel\Exception\HttpException" + ], + ValidationException::class => [ + "Illuminate\Validation\ValidationException" + ], + ], + + /* + * if the app is not in debug mode. all unknown exceptions will be handled by this. + */ + "internal_error_handler" => ServerInternalException::class, ]; diff --git a/src/HandlerFactory.php b/src/HandlerFactory.php new file mode 100644 index 0000000..b21fe13 --- /dev/null +++ b/src/HandlerFactory.php @@ -0,0 +1,69 @@ +debugMode = app()->hasDebugModeEnabled(); + $this->loadHandlers(); + } + + protected function loadHandlers(): void + { + $this->handlers = $this->loadHandlersFromConfigs(); + $this->internalErrorHandler = Config::get("api-error-handler.internal_error_handler") ?? ServerInternalException::class; + } + + private function loadHandlersFromConfigs(): array + { + return array_map( + fn($exceptions)=> is_array($exceptions) ? $exceptions : [$exceptions], + Config::get("api-error-handler.handlers") ?? [] + ); + } + + private function exceptionHasHandler($exception): bool + { + return !is_null($this->findHandlerByException($exception)); + } + + private function findHandlerByException($exception): string|null + { + $exceptionClassName = get_class($exception); + foreach ($this->handlers as $handler => $exceptions){ + if (in_array($exceptionClassName,$exceptions)){ + return $handler; + } + } + return null; + } + + protected function makeHandler(string $handlerClassName,$exception) + { + return new $handlerClassName($exception); + } + + public function getHandler($exception): ExceptionAbstract + { + $handlerClassName = $this->findHandlerByException($exception); + if (!$handlerClassName){ + $handlerClassName = $this->debugMode ? + DefaultException::class : + $this->internalErrorHandler; + } + + return $this->makeHandler($handlerClassName,$exception); + } +} diff --git a/src/Traits/ApiErrorHandler.php b/src/Traits/ApiErrorHandler.php index 8eb09d8..226fc89 100644 --- a/src/Traits/ApiErrorHandler.php +++ b/src/Traits/ApiErrorHandler.php @@ -2,28 +2,28 @@ namespace hamidreza2005\LaravelApiErrorHandler\Traits; -use hamidreza2005\LaravelApiErrorHandler\Exceptions\DefaultException; -use hamidreza2005\LaravelApiErrorHandler\Exceptions\ServerInternalException; +use hamidreza2005\LaravelApiErrorHandler\HandlerFactory; use Illuminate\Http\JsonResponse; -use Illuminate\Support\Facades\Response; +use Illuminate\Http\Response; trait ApiErrorHandler { - public function handleError($exception): JsonResponse + public function handle($exception): JsonResponse { - $exceptions = config("api-error-handler") ?? []; - $class = array_key_exists(get_class($exception),$exceptions) ? - $exceptions[get_class($exception)] : - (config('app.debug') ? DefaultException::class : ServerInternalException::class - ); - $handler = new $class($exception); + $handler = (new HandlerFactory())->getHandler($exception); $handler->handleStatusCode(); $handler->handleMessage(); - return $this->errorResponse(["error"=>$handler->getMessage()],$handler->getStatusCode(),["Content-Type"=>"application/json"]); + return $this->errorResponse($handler->getMessage(),[],$handler->getStatusCode(),["Content-Type"=>"application/json"]); } - protected function errorResponse(array $data,int $statusCode, array $headers): JsonResponse + protected function errorResponse(string $message, array $data = [] ,int $statusCode = Response::HTTP_BAD_REQUEST, array $headers = []): JsonResponse { - return Response::json($data,$statusCode,$headers); + $result = [ + "message" => $message, + "success" => false, + "data" => $data, + "status" => $statusCode + ]; + return response()->json($result,$statusCode,$headers); } } diff --git a/tests/Unit/HandlerFactoryTest.php b/tests/Unit/HandlerFactoryTest.php new file mode 100644 index 0000000..5bd853a --- /dev/null +++ b/tests/Unit/HandlerFactoryTest.php @@ -0,0 +1,79 @@ +with('app.debug') + ->andReturn(true); + + Config::shouldReceive("get") + ->with("api-error-handler.handlers"); + + Config::shouldReceive("get") + ->with("api-error-handler.internal_error_handler") + ->andReturn(ServerInternalException::class); + + $exceptionClass = new \Exception("fdf"); + $handler = (new HandlerFactory())->getHandler($exceptionClass); + $this->assertTrue(true); // just so phpunit doesn't give warning + } + + public function test_handler_class_gives_the_right_handler() + { + Config::set("api-error-handler.handlers",[ + AccessDeniedException::class => \Exception::class + ]); + + $exception = new \Exception("Error"); + $handler = (new HandlerFactory())->getHandler($exception); + $this->assertInstanceOf(AccessDeniedException::class,$handler); + } + + public function test_handler_can_have_multiple_exceptions() + { + Config::set("api-error-handler.handlers",[ + AccessDeniedException::class => [\Exception::class,AccessDeniedHttpException::class] + ]); + + $exception = new AccessDeniedHttpException("Error"); + $handler = (new HandlerFactory())->getHandler($exception); + $this->assertInstanceOf(AccessDeniedException::class,$handler); + } + + public function test_handler_class_gives_server_internal_if_unknown_exception_occurs_when_debug_mode_is_false() + { + Config::set("app.debug",false); + Config::set("api-error-handler.handlers",[ + AccessDeniedException::class => \Exception::class + ]); + + $exception = new AccessDeniedHttpException("Error"); + $handler = (new HandlerFactory())->getHandler($exception); + $this->assertInstanceOf(ServerInternalException::class,$handler); + } + public function test_handler_class_gives_rised_exception_if_unknown_exception_occurs_when_debug_mode_is_false() + { + Config::set("app.debug",true); + Config::set("api-error-handler.handlers",[ + AccessDeniedException::class => \Exception::class + ]); + + $exception = new AccessDeniedHttpException("Error"); + $handler = (new HandlerFactory())->getHandler($exception); + $this->assertInstanceOf(DefaultException::class,$handler); + } +} diff --git a/tests/Unit/SimpleTest.php b/tests/Unit/SimpleTest.php deleted file mode 100644 index dd30698..0000000 --- a/tests/Unit/SimpleTest.php +++ /dev/null @@ -1,13 +0,0 @@ -assertTrue(true); - } -}