|
1 | | -Api-Problem: ZF2 Module for API-Problem representations |
2 | | -======================================================= |
| 1 | +ZF Api Problem |
| 2 | +============== |
3 | 3 |
|
4 | 4 | [](https://travis-ci.org/zfcampus/zf-api-problem) |
5 | | -[](https://coveralls.io/r/zfcampus/zf-api-problem) |
| 5 | + |
| 6 | +Introduction |
| 7 | +------------ |
6 | 8 |
|
7 | 9 | This module provides data structures and rendering for the API-Problem format. |
8 | 10 |
|
9 | | -- [Problem API](http://tools.ietf.org/html/draft-nottingham-http-problem-05), |
10 | | - used for reporting API problems |
| 11 | +- [Problem Details for HTTP APIs](http://tools.ietf.org/html/draft-nottingham-http-problem-06), |
| 12 | + used for reporting API problems. |
| 13 | + |
| 14 | +Installation |
| 15 | +------------ |
| 16 | + |
| 17 | +Run the following `composer` command: |
| 18 | + |
| 19 | +```console |
| 20 | +$ composer require "zfcampus/zf-api-problem:~1.0-dev" |
| 21 | +``` |
| 22 | + |
| 23 | +Alternately, manually add the following to your `composer.json`, in the `require` section: |
| 24 | + |
| 25 | +```javascript |
| 26 | +"require": { |
| 27 | + "zfcampus/zf-api-problem": "~1.0-dev" |
| 28 | +} |
| 29 | +``` |
| 30 | + |
| 31 | +And then run `composer update` to ensure the module is installed. |
| 32 | + |
| 33 | +Finally, add the module name to your project's `config/application.config.php` under the `modules` |
| 34 | +key: |
| 35 | + |
| 36 | +```php |
| 37 | +return array( |
| 38 | + /* ... */ |
| 39 | + 'modules' => array( |
| 40 | + /* ... */ |
| 41 | + 'ZF\ApiProblem', |
| 42 | + ), |
| 43 | + /* ... */ |
| 44 | +); |
| 45 | +``` |
| 46 | + |
| 47 | +Configuration |
| 48 | +------------- |
| 49 | + |
| 50 | +### User Configuration |
| 51 | + |
| 52 | +The top-level configuration key for user configuration of this module is `zf-api-problem`. |
| 53 | + |
| 54 | +#### Key: `accept_filters` |
| 55 | + |
| 56 | +// Accept types that should allow ApiProblem responses |
| 57 | + |
| 58 | +#### Key: `render_error_controllers` |
| 59 | + |
| 60 | +// Array of controller service names that should enable the ApiProblem render.error listener |
| 61 | + |
| 62 | + |
| 63 | +### System Configuration |
| 64 | + |
| 65 | +The following configuration is provided in `config/module.config.php` to enable the module to |
| 66 | +function: |
| 67 | + |
| 68 | +```php |
| 69 | +'service_manager' => array( |
| 70 | + 'aliases' => array( |
| 71 | + 'ZF\ApiProblem\ApiProblemListener' => 'ZF\ApiProblem\Listener\ApiProblemListener', |
| 72 | + 'ZF\ApiProblem\RenderErrorListener' => 'ZF\ApiProblem\Listener\RenderErrorListener', |
| 73 | + 'ZF\ApiProblem\ApiProblemRenderer' => 'ZF\ApiProblem\View\ApiProblemRenderer', |
| 74 | + 'ZF\ApiProblem\ApiProblemStrategy' => 'ZF\ApiProblem\View\ApiProblemStrategy', |
| 75 | + ), |
| 76 | + 'factories' => array( |
| 77 | + 'ZF\ApiProblem\Listener\ApiProblemListener' => 'ZF\ApiProblem\Factory\ApiProblemListenerFactory', |
| 78 | + 'ZF\ApiProblem\Listener\RenderErrorListener' => 'ZF\ApiProblem\Factory\RenderErrorListenerFactory', |
| 79 | + 'ZF\ApiProblem\Listener\SendApiProblemResponseListener' => 'ZF\ApiProblem\Factory\SendApiProblemResponseListenerFactory', |
| 80 | + 'ZF\ApiProblem\View\ApiProblemRenderer' => 'ZF\ApiProblem\Factory\ApiProblemRendererFactory', |
| 81 | + 'ZF\ApiProblem\View\ApiProblemStrategy' => 'ZF\ApiProblem\Factory\ApiProblemStrategyFactory', |
| 82 | + ), |
| 83 | +), |
| 84 | +'view_manager' => array( |
| 85 | + // Enable this in your application configuration in order to get full |
| 86 | + // exception stack traces in your API-Problem responses. |
| 87 | + 'display_exceptions' => false, |
| 88 | +), |
| 89 | +``` |
| 90 | + |
| 91 | +ZF2 Events |
| 92 | +---------- |
| 93 | + |
| 94 | +### Listeners |
| 95 | + |
| 96 | +#### `ZF\ApiProblem\Listener\ApiProblemListener` |
| 97 | + |
| 98 | +The `ApiProblemListener` attaches to three events in the MVC lifecycle: |
| 99 | + |
| 100 | +- `MvcEvent::EVENT_DISPATCH` as a _shared_ listener on `Zend\Stdlib\DispatchableInterface` with a |
| 101 | + priority of `100`. |
| 102 | +- `MvcEvent::EVENT_DISPATCH_ERROR` with a priority of `100`. |
| 103 | +- `MvcEvent::EVENT_RENDER` with a priority of `1000`. |
| 104 | + |
| 105 | +If the current `Accept` media type does not match the configured API-Problem media types (by |
| 106 | +default, these are `application/json` and `application/*+json`), then this listener returns without |
| 107 | +taking any action. |
| 108 | + |
| 109 | +When this listener does take action, the purposes are threefold: |
| 110 | + |
| 111 | +- Before dispatching, the `render_error_controllers` configuration value is consulted to determine |
| 112 | + if the `ZF\ApiProblem\Listener\RenderErrorListener` should be attached; see |
| 113 | + [RenderErrorListener](#rendererrorlistener) for more information. |
| 114 | +- After dispatching, detects the type of response from the controller; if it is already an |
| 115 | + `ApiProblem` model, it continues without doing anything. If an exception was thrown during |
| 116 | + dispatch, it converts the response to an API-Problem response with some information from the |
| 117 | + exception. |
| 118 | +- If a dispatch error occurred, and the `Accept` type is in the set defined for API-Problems, it |
| 119 | + attempts to cast the dispatch exception into an API-Problem response. |
| 120 | + |
| 121 | +#### `ZF\ApiProblem\Listener\RenderErrorListener` |
| 122 | + |
| 123 | +This listener is attached to `MvcEvent::EVENT_RENDER_ERROR` at priority `100`. This listener is |
| 124 | +conditionally attached by `ZF\ApiProblem\Listener\ApiProblemListener` for controllers that require |
| 125 | +API Problem responses. With a priority of `100`, this ensures that this listener runs before the |
| 126 | +default ZF2 listener on this event. In cases when it does run, it will cast an exception into an |
| 127 | +API-problem response. |
| 128 | + |
| 129 | +#### `ZF\ApiProblem\Listener\SendApiProblemResponseListener` |
| 130 | + |
| 131 | +This listener is attached to `SendResponseEvent::EVENT_SEND_RESPONSE` at priority `-500`. The |
| 132 | +primary purpose of this listener is, on detection of an API-Problem response, to send appropriate |
| 133 | +headers and the problem details as the content body. If the `view_manager`'s `display_exceptions` |
| 134 | +setting is enabled, the listener will determine if the API-Problem represents an application |
| 135 | +exception, and, if so, inject the exception trace as part of the serialized response. |
| 136 | + |
| 137 | +ZF2 Services |
| 138 | +------------ |
| 139 | + |
| 140 | +### Event Services |
| 141 | + |
| 142 | +- `ZF\ApiProblem\Listener\ApiProblemListener` |
| 143 | +- `ZF\ApiProblem\Listener\RenderErrorListener` |
| 144 | +- `ZF\ApiProblem\Listener\SendApiProblemResponseListener` |
| 145 | + |
| 146 | +### View Services |
| 147 | + |
| 148 | +#### `ZF\ApiProblem\View\ApiProblemRenderer` |
| 149 | + |
| 150 | +This service extends the `JsonRenderer` service from the ZF2 MVC layer. Its primary responsibility |
| 151 | +is to decorate JSON rendering with the ability to optionally output stack traces. |
| 152 | + |
| 153 | +#### `ZF\ApiProblem\View\ApiProblemStrategy` |
| 154 | + |
| 155 | +This service is a view strategy that detects a `ZF\ApiProblem\View\ApiProblemModel`; when detected, |
| 156 | +it selects the [ApiProblemRender](#zfapiproblemviewapiproblemrenderer), and injects the response |
| 157 | +with a `Content-Type` header that contains the `application/problem+json` media type. This is |
| 158 | +similar in nature to Zend Framework 2's `JsonStrategy`. |
| 159 | + |
| 160 | +### Models |
| 161 | + |
| 162 | +#### `ZF\ApiProblem\ApiProblem` |
| 163 | + |
| 164 | +An instance of `ZF\ApiProblem\ApiProblem` serves the purpose of modeling the kind of problem that is |
| 165 | +encountered. An instance of `ApiProblem` is typically wrapped in an |
| 166 | +[ApiProblemResponse](#zfapiproblemapiproblemresponse). Most information can be passed into the |
| 167 | +constructor: |
| 168 | + |
| 169 | +```php |
| 170 | +class ApiProblem { |
| 171 | + public function __construct( |
| 172 | + $status, |
| 173 | + $detail, |
| 174 | + $type = null, |
| 175 | + $title = null, |
| 176 | + array $additional = array() |
| 177 | + ) { |
| 178 | + /* ... */ |
| 179 | + } |
| 180 | +} |
| 181 | +``` |
| 182 | + |
| 183 | +For example: |
| 184 | + |
| 185 | +```php |
| 186 | +new ApiProblem(404, 'Entity not found'); |
| 187 | + |
| 188 | +// or |
| 189 | + |
| 190 | +new ApiProblem(424, $exceptionInstance); |
| 191 | +``` |
| 192 | + |
| 193 | +#### `ZF\ApiProblem\ApiProblemResponse` |
| 194 | + |
| 195 | +An instance of `ZF\ApiProblem\ApiProblemResponse` can be returned from any controller service or ZF2 |
| 196 | +MVC event in order to short-circuit the MVC lifecycle and immediately return a response. When it |
| 197 | +is, the response will be converted to the proper JSON structure for an API-Problem, and the |
| 198 | +`Content-Type` header will be set to the `application/problem+json` media type. |
| 199 | + |
| 200 | +For example: |
| 201 | + |
| 202 | +```php |
| 203 | +use Zend\Mvc\Controller\AbstractActionController; |
| 204 | +use ZF\ApiProblem\ApiProblem; |
| 205 | +use ZF\ApiProblem\ApiProblemResponse; |
| 206 | + |
| 207 | +class MyController extends AbstractActionController |
| 208 | +{ |
| 209 | + /* ... */ |
| 210 | + public function fetch($id) |
| 211 | + { |
| 212 | + $entity = $this->model->fetch($id); |
| 213 | + if (! $entity) { |
| 214 | + return new ApiProblemResponse(ApiProblem(404, 'Entity not found')); |
| 215 | + } |
| 216 | + return $entity; |
| 217 | + } |
| 218 | +} |
| 219 | +``` |
0 commit comments