├── tests └── .gitkeep ├── .gitignore ├── composer.json ├── LICENSE ├── src ├── Exceptions │ ├── BadRequestException.php │ ├── ConflictException.php │ ├── NotFoundException.php │ ├── ProcessingException.php │ ├── ForbiddenException.php │ ├── NoContentException.php │ ├── InternalErrorException.php │ ├── NotModifiedException.php │ ├── RequestTimeoutException.php │ ├── UnauthorizedException.php │ ├── RequestTooLongException.php │ ├── PreconditionFailedException.php │ ├── UnprocessableEntityException.php │ ├── PaymentRequiredException.php │ ├── HttpException.php │ └── BaseException.php └── Handler.php └── README.md /tests/.gitkeep: -------------------------------------------------------------------------------- 1 | @todo 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | phpunit.xml 3 | vendor 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kamranahmedse/laravel-faulty", 3 | "description": "A minimal package to assist in returning RESTful exceptions in your APIs.", 4 | "keywords": [ 5 | "laravel", 6 | "lumen", 7 | "exceptions", 8 | "exception", 9 | "errors", 10 | "error", 11 | "whoops", 12 | "laravel faulty", 13 | "api problem" 14 | ], 15 | "type": "library", 16 | "autoload": { 17 | "psr-4": { 18 | "KamranAhmed\\Faulty\\": "src/" 19 | } 20 | }, 21 | "require": { 22 | "filp/whoops": "^2.1" 23 | }, 24 | "license": "MIT", 25 | "authors": [ 26 | { 27 | "name": "Kamran Ahmed", 28 | "email": "kamranahmed.se@gmail.com" 29 | } 30 | ], 31 | "minimum-stability": "dev" 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kamran Ahmed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Exceptions/BadRequestException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class BadRequestException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 400; 16 | 17 | /** @var string */ 18 | protected $title = 'Bad Request'; 19 | 20 | /** @var string */ 21 | protected $detail; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * BadRequestException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/ConflictException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ConflictException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 409; 16 | 17 | /** @var string */ 18 | protected $title = 'Conflict Error'; 19 | 20 | /** @var string */ 21 | protected $detail = ''; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * ConflictException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/NotFoundException.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class NotFoundException extends BaseException 14 | { 15 | /** @var int */ 16 | protected $status = 404; 17 | 18 | /** @var string */ 19 | protected $title = 'Not found'; 20 | 21 | /** @var string */ 22 | protected $detail; 23 | 24 | /** @var string */ 25 | protected $instance; 26 | 27 | /** @var string */ 28 | protected $type; 29 | 30 | /** 31 | * NotFoundException constructor. 32 | * 33 | * @param string $detail 34 | * @param string $title 35 | * @param string $instance 36 | * @param string $type 37 | */ 38 | public function __construct($detail, $title = '', $instance = '', $type = '') 39 | { 40 | $this->detail = $detail ?: $this->title; 41 | $this->title = $title ?: $this->title; 42 | $this->instance = $instance; 43 | $this->type = $type; 44 | 45 | parent::__construct($this->detail); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Exceptions/ProcessingException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ProcessingException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 102; 16 | 17 | /** @var string */ 18 | protected $title = 'Processing'; 19 | 20 | /** @var string */ 21 | protected $detail; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * BadRequestException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/ForbiddenException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ForbiddenException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 403; 16 | 17 | /** @var string */ 18 | protected $title = 'Forbidden Exception'; 19 | 20 | /** @var string */ 21 | protected $detail = ''; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * ForbiddenException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/NoContentException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class NoContentException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 204; 16 | 17 | /** @var string */ 18 | protected $title = 'No content available'; 19 | 20 | /** @var string */ 21 | protected $detail; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * BadRequestException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/InternalErrorException.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class InternalErrorException extends BaseException 14 | { 15 | /** @var int */ 16 | protected $status = 500; 17 | 18 | /** @var string */ 19 | protected $title = 'Internal Error'; 20 | 21 | /** @var string */ 22 | protected $detail; 23 | 24 | /** @var string */ 25 | protected $instance; 26 | 27 | /** @var string */ 28 | protected $type; 29 | 30 | /** 31 | * NotFoundException constructor. 32 | * 33 | * @param string $detail 34 | * @param string $title 35 | * @param string $instance 36 | * @param string $type 37 | */ 38 | public function __construct($detail, $title = '', $instance = '', $type = '') 39 | { 40 | $this->detail = $detail ?: $this->title; 41 | $this->title = $title ?: $this->title; 42 | $this->instance = $instance; 43 | $this->type = $type; 44 | 45 | parent::__construct($this->detail); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Exceptions/NotModifiedException.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class NotModifiedException extends BaseException 14 | { 15 | /** @var int */ 16 | protected $status = 304; 17 | 18 | /** @var string */ 19 | protected $title = 'Resource not modified'; 20 | 21 | /** @var string */ 22 | protected $detail; 23 | 24 | /** @var string */ 25 | protected $instance; 26 | 27 | /** @var string */ 28 | protected $type; 29 | 30 | /** 31 | * NotFoundException constructor. 32 | * 33 | * @param string $detail 34 | * @param string $title 35 | * @param string $instance 36 | * @param string $type 37 | */ 38 | public function __construct($detail, $title = '', $instance = '', $type = '') 39 | { 40 | $this->detail = $detail ?: $this->title; 41 | $this->title = $title ?: $this->title; 42 | $this->instance = $instance; 43 | $this->type = $type; 44 | 45 | parent::__construct($this->detail); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Exceptions/RequestTimeoutException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class RequestTimeoutException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 408; 16 | 17 | /** @var string */ 18 | protected $title = 'Request timed out'; 19 | 20 | /** @var string */ 21 | protected $detail; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * BadRequestException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/UnauthorizedException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UnauthorizedException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 401; 16 | 17 | /** @var string */ 18 | protected $title = 'Unauthorized Exception'; 19 | 20 | /** @var string */ 21 | protected $detail; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * UnauthorizedException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/RequestTooLongException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class RequestTooLongException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 414; 16 | 17 | /** @var string */ 18 | protected $title = 'Request URI too long'; 19 | 20 | /** @var string */ 21 | protected $detail; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * PreconditionFailedException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/PreconditionFailedException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class PreconditionFailedException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 412; 16 | 17 | /** @var string */ 18 | protected $title = 'Precondition failed'; 19 | 20 | /** @var string */ 21 | protected $detail; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * PreconditionFailedException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/UnprocessableEntityException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UnprocessableEntityException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 422; 16 | 17 | /** @var string */ 18 | protected $title = 'Unprocessable Entity'; 19 | 20 | /** @var string */ 21 | protected $detail; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * UnprocessableEntityException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/PaymentRequiredException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class PaymentRequiredException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 402; 16 | 17 | /** @var string */ 18 | protected $title = 'Payment required'; 19 | 20 | /** @var string */ 21 | protected $detail = 'Payment is required to perform this action'; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * BadRequestException constructor. 31 | * 32 | * @param string $detail 33 | * @param string $title 34 | * @param string $instance 35 | * @param string $type 36 | */ 37 | public function __construct($detail, $title = '', $instance = '', $type = '') 38 | { 39 | $this->detail = $detail ?: $this->title; 40 | $this->title = $title ?: $this->title; 41 | $this->instance = $instance; 42 | $this->type = $type; 43 | 44 | parent::__construct($this->detail); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Exceptions/HttpException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class HttpException extends BaseException 13 | { 14 | /** @var int */ 15 | protected $status = 500; 16 | 17 | /** @var string */ 18 | protected $title = 'Problem Occurred'; 19 | 20 | /** @var string */ 21 | protected $detail = "An error occurred and the process couldn't be processed"; 22 | 23 | /** @var string */ 24 | protected $instance; 25 | 26 | /** @var string */ 27 | protected $type; 28 | 29 | /** 30 | * BadRequestException constructor. 31 | * 32 | * @param string $title 33 | * @param integer $status 34 | * @param string $detail 35 | * @param string $instance 36 | * @param string $type 37 | */ 38 | public function __construct($title = '', $status = 500, $detail = '', $instance = '', $type = '') 39 | { 40 | $this->title = $title ?: $this->title; 41 | $this->status = $status; 42 | $this->detail = $detail ?: $this->detail; 43 | $this->type = $type; 44 | $this->instance = $instance; 45 | 46 | parent::__construct($this->detail); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Exceptions/BaseException.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | abstract class BaseException extends Exception 19 | { 20 | /** @var int */ 21 | protected $status; 22 | 23 | /** @var string */ 24 | protected $title; 25 | 26 | /** @var string */ 27 | protected $detail; 28 | 29 | /** @var string */ 30 | protected $type; 31 | 32 | /** @var string */ 33 | protected $instance; 34 | 35 | /** @var array */ 36 | protected $additionalDetail; 37 | 38 | /** 39 | * @param string $message 40 | * 41 | * @throws \KamranAhmed\Faulty\Exceptions\InternalErrorException 42 | */ 43 | public function __construct($message) 44 | { 45 | if (!is_scalar($message)) { 46 | $message = $this->title; 47 | } 48 | 49 | parent::__construct($message); 50 | } 51 | 52 | /** 53 | * Get the status 54 | * 55 | * @return int 56 | */ 57 | public function getStatus() 58 | { 59 | return (int)$this->status; 60 | } 61 | 62 | /** 63 | * @param $status 64 | * 65 | * @return $this 66 | */ 67 | public function setStatus($status) 68 | { 69 | $this->status = $status; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * @return string 76 | */ 77 | public function getTitle() 78 | { 79 | return $this->title; 80 | } 81 | 82 | /** 83 | * @param $title 84 | * 85 | * @return $this 86 | */ 87 | public function setTitle($title) 88 | { 89 | $this->title = $title; 90 | 91 | return $this; 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | public function getDetail() 98 | { 99 | return $this->detail; 100 | } 101 | 102 | /** 103 | * @param $detail 104 | * 105 | * @return $this 106 | */ 107 | public function setDetail($detail) 108 | { 109 | $this->detail = $detail; 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * @return string 116 | */ 117 | public function getType() 118 | { 119 | return $this->type; 120 | } 121 | 122 | /** 123 | * @param $type 124 | * 125 | * @return $this 126 | */ 127 | public function setType($type) 128 | { 129 | $this->type = $type; 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * @return string 136 | */ 137 | public function getInstance() 138 | { 139 | return $this->instance; 140 | } 141 | 142 | /** 143 | * @param $instance 144 | * 145 | * @return $this 146 | */ 147 | public function setInstance($instance) 148 | { 149 | $this->instance = $instance; 150 | 151 | return $this; 152 | } 153 | 154 | /** 155 | * @param array $additionalDetail 156 | * 157 | * @return $this 158 | * @throws \KamranAhmed\Faulty\Exceptions\InternalErrorException 159 | */ 160 | public function setAdditional($additionalDetail) 161 | { 162 | if (!is_array($additionalDetail)) { 163 | throw new InternalErrorException('Additional detail must be array'); 164 | } 165 | 166 | $this->additionalDetail = $additionalDetail; 167 | 168 | return $this; 169 | } 170 | 171 | /** 172 | * Fails with the current exception object 173 | * 174 | * @throws \KamranAhmed\Faulty\Exceptions\BaseException 175 | */ 176 | public function toss() 177 | { 178 | throw $this; 179 | } 180 | 181 | /** 182 | * Return the Exception as an array 183 | * 184 | * @return array 185 | */ 186 | public function toArray() 187 | { 188 | $error = [ 189 | 'status' => $this->status, 190 | 'title' => $this->title, 191 | 'detail' => $this->detail, 192 | 'type' => $this->type ?: 'https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html', 193 | 'instance' => $this->instance, 194 | ]; 195 | 196 | $error = array_merge($error, $this->additionalDetail ?: []); 197 | 198 | return array_filter($error); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Faulty - RESTful Exceptions 2 | 3 | > Automatically turns your thrown exceptions (HTTP/non-HTTP) to the JSON response while conforming to API problem specification. 4 | 5 | A Laravel/Lumen package that lets you handle the API problems with ease. 6 | 7 | Faulty provides a straightforward implementation of [IETF Problem Specification](https://tools.ietf.org/html/draft-nottingham-http-problem-07) and turns your exceptions to be returned in the below format with the content type of `application/problem+json` 8 | 9 | ```json 10 | { 11 | "status": 403, 12 | "type": "http://example.com/problems/out-of-credit", 13 | "title": "You do not have enough credit.", 14 | "detail": "Your current balance is 30, but that costs 50.", 15 | "instance": "http://example.net/account/12345/logs?id=233" 16 | } 17 | ``` 18 | Where 19 | - `type` is the absolute URI that identifies the type of problem 20 | - `title` is the summary of problem 21 | - `status` is the status code 22 | - `detail` is human readable explanation specific to problem 23 | - `instance` is the absolute URI that identifies the specific occurrence of the problem 24 | 25 | ## Installation 26 | 27 | Run the below command 28 | 29 | ``` 30 | composer require kamranahmedse/laravel-faulty 31 | ``` 32 | 33 | Make your exception handler i.e. `App\Exceptions\Handler` that can be found at `app\Exceptions\Handler.php` extend from the Faulty's handler i.e. 34 | 35 | ```php 36 | 37 | use KamranAhmed\Faulty\Handler as FaultyHandler; 38 | 39 | class Handler extends FaultyHandler { 40 | // ... 41 | } 42 | ``` 43 | 44 | And that's it. You are all set to use Faulty. 45 | 46 | ##Configuration 47 | Faulty relies on the following environment configurations 48 | 49 | - `APP_DEBUG` : If `true`, exceptions will be rendered with whoops, if false JSON will be returned. **Defaults to `false`** 50 | - `APP_DEBUG_TRACE` : If true, stack trace will be included in the application errors. **Defaults to `true`** 51 | 52 | ## Usage 53 | 54 | For HTTP exceptions to be rendered properly with the proper status codes, you should use the exception classes provided by faulty i.e. the ones available in `Faulty\Exceptions` namespace or use the relevant ones provided by the Symfony's HTTP component i.e. the ones available under `Symfony\Component\HttpKernel\Exception` 55 | 56 | ###Throwing Exceptions 57 | 58 | All the exception classes have the below signature 59 | 60 | ```php 61 | use KamranAhmed\Faulty\Exceptions\[ProblemType]Exception; 62 | [ProblemType]Exception($detail, $title = '', $instance = '', $type = '') 63 | ``` 64 | 65 | Here are some of the provided exception classes 66 | 67 | ```php 68 | // Include the exception classes from the given namespace 69 | 70 | throw new BadRequestException('Invalid request data'); 71 | throw new ConflictException('Same request is already pending'); 72 | throw new ForbiddenException('You are not allowed to perform this action'); 73 | throw new InternalErrorException('Exports directory isn\'t writable'); 74 | throw new NoContentException('Deletion request successfuly accepted'); 75 | throw new NotFoundException('Item not found'); 76 | throw new NotModifiedException('..'); 77 | throw new PaymentRequiredException('..'); 78 | throw new PreconditionFailedException('..'); 79 | throw new ProcessingException('..'); 80 | throw new RequestTimeoutException('..'); 81 | throw new RequestTooLongException('..'); 82 | throw new UnauthorizedException('..'); 83 | throw new UnprocessableEntityException('..'); 84 | ``` 85 | 86 | Also, if you would like to return any response for which the exception class isn't available, you can use the `HttpException` class i.e. 87 | 88 | ```php 89 | use KamranAhmed\Faulty\Exceptions\HttpException; 90 | 91 | throw new HttpException($title = '', $status = 500, $detail = '', $instance = '', $type = ''); 92 | ``` 93 | 94 | ### Syntactic Sugar 95 | Also, for any of the exception classes above, you can use the below syntax as well. 96 | 97 | ```php 98 | $typeUrl = route('api.problem', ['type' => 'forbidden']); 99 | $occurence = route('account.error', ['account_id' => 'A837332A', 'log_id' => 34]); 100 | 101 | (new ForbiddenException("Your account doesn't have the balance of 50 USD")) 102 | ->setTitle('Balance too low) 103 | ->setType($problemRoute) 104 | ->setInstance($occurence) 105 | ->toss(); 106 | ``` 107 | 108 | Also, if you would like to send additional data in response, call the method `setAdditional([])` on the error object while passing the additional detail i.e. 109 | 110 | ```php 111 | (new ForbiddenException("Your account doesn't have the balance of 50 USD")) 112 | ->setTitle('Balance too low) 113 | ->setAdditional([ 114 | 'current_balance' => 40, 115 | 'required_balance' => 50, 116 | 'item_detail' => $itemArray 117 | ]) 118 | ->toss(); 119 | ``` 120 | 121 | ## Contributing 122 | Feel free to fork, enhance, create PR and lock issues. 123 | 124 | ## License 125 | MIT © [Kamran Ahmed](http://kamranahmed.info) 126 | -------------------------------------------------------------------------------- /src/Handler.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class Handler extends LumenExceptionHandler 28 | { 29 | /** 30 | * Convert the Exception into a JSON HTTP Response 31 | * 32 | * @param Request $request 33 | * @param Exception $e 34 | * 35 | * @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response 36 | */ 37 | public function render($request, Exception $e) 38 | { 39 | $debugMode = env('APP_DEBUG', false); 40 | 41 | // If debug is enabled, we are okay with sending back the views 42 | if ($debugMode && (php_sapi_name() !== 'cli')) { 43 | return $this->renderExceptionWithWhoops($e); 44 | } 45 | 46 | return $this->handle($request, $e); 47 | } 48 | 49 | /** 50 | * Render an exception using Whoops. 51 | * 52 | * @param Exception $e 53 | * 54 | * @return Response 55 | */ 56 | protected function renderExceptionWithWhoops(Exception $e) 57 | { 58 | $statusCode = 500; 59 | if (method_exists($e, 'getStatusCode')) { 60 | $statusCode = $e->getStatusCode(); 61 | } 62 | 63 | $headers = []; 64 | if (method_exists($e, 'getHeaders')) { 65 | $headers = $e->getHeaders(); 66 | } 67 | 68 | $whoops = new Run; 69 | $whoops->pushHandler(new PrettyPageHandler()); 70 | 71 | return new Response($whoops->handleException($e), $statusCode, $headers); 72 | } 73 | 74 | /** 75 | * Handles the exceptions thrown in the application. Checks if the exception 76 | * is one of the Faulty's exception (i.e. BaseException's Child) then returns 77 | * the array with status and render. If there is any other e.g. fatal or anything 78 | * 79 | * @param $request 80 | * @param Exception $e 81 | * 82 | * @return Response|\Symfony\Component\HttpFoundation\Response 83 | */ 84 | public function handle($request, Exception $e) 85 | { 86 | $data = $this->getExceptionDefaults($e); 87 | 88 | if ($e instanceOf BaseException) { 89 | $data = $e->toArray(); 90 | } else if ($e instanceof HttpException) { 91 | 92 | if ($e instanceof NotFoundException) { 93 | $detail = 'Resource not found'; 94 | } else if ($e instanceof MethodNotAllowedHttpException) { 95 | $detail = 'Method not allowed'; 96 | } else { 97 | $detail = $this->generateHttpExceptionMessage($e); 98 | } 99 | 100 | $data = [ 101 | 'status' => $e->getStatusCode(), 102 | 'title' => $detail, 103 | 'detail' => $detail, 104 | ]; 105 | } else if ($e instanceof ValidationException) { 106 | parent::render($request, $e); 107 | } 108 | 109 | // Include the trace in response iff 110 | // - Environment allows it `APP_STACKTRACE` 111 | // - It is an internal error 112 | // - Exception has a trace string method 113 | if ( 114 | env('APP_DEBUG_TRACE', true) && 115 | $data['status'] === 500 && 116 | method_exists($e, 'getTraceAsString') 117 | ) { 118 | $data['trace'] = $e->getTraceAsString(); 119 | } 120 | 121 | return response()->json($data, $data['status'], ['Content-Type' => 'application/problem+json']); 122 | } 123 | 124 | /** 125 | * Gets the default details for the exception, if suitable match isn't found 126 | * 127 | * @param Exception $e 128 | * 129 | * @return array 130 | */ 131 | protected function getExceptionDefaults(Exception $e) 132 | { 133 | return [ 134 | 'status' => 500, 135 | 'title' => 'Uncaught Error', 136 | 'detail' => $e->getMessage(), 137 | 'type' => 'https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html', 138 | ]; 139 | } 140 | 141 | /** 142 | * @param \Symfony\Component\HttpKernel\Exception\HttpException $e 143 | * 144 | * @return string 145 | */ 146 | private function generateHttpExceptionMessage(HttpException $e) 147 | { 148 | $class = get_class($e); 149 | $parts = explode('\\', $class); 150 | 151 | $title = $parts[count($parts) - 1] ?? $class; 152 | $title = str_replace('HttpException', '', $title); 153 | 154 | return $title; 155 | } 156 | } 157 | --------------------------------------------------------------------------------