@@ -16,27 +16,25 @@ try {
1616## Error Providers
1717
1818Exceptions can implement the ` Tobyz\JsonApiServer\Exception\ErrorProvider `
19- interface to determine what status code will be used in the response, and any
20- JSON: API error objects to be rendered in the document.
19+ interface to determine what status code will be used in the response, and define
20+ a JSON: API error object to be rendered in the document.
2121
2222The interface defines two methods:
2323
24- - ` getJsonApiStatus ` which must return a string.
25- - ` getJsonApiErrors ` which must return an array of JSON: API error objects.
24+ - ` getJsonApiStatus ` which must return the HTTP status code applicable to the
25+ exception as a string.
26+ - ` getJsonApiError ` which must return a JSON: API error object.
2627
2728``` php
28- use JsonApiPhp\JsonApi\Error;
2929use Tobyz\JsonApiServer\Exception\ErrorProvider;
3030
3131class ImATeapotException implements ErrorProvider
3232{
33- public function getJsonApiErrors (): array
33+ public function getJsonApiError (): array
3434 {
3535 return [
36- [
37- 'title' => "I'm a teapot",
38- 'status' => $this->getJsonApiStatus(),
39- ],
36+ 'title' => "I'm A Teapot",
37+ 'status' => $this->getJsonApiStatus(),
4038 ];
4139 }
4240
@@ -50,8 +48,138 @@ class ImATeapotException implements ErrorProvider
5048Exceptions that do not implement this interface will result in a generic
5149` 500 Internal Server Error ` response.
5250
51+ ### Creating Custom Exceptions
52+
53+ The simplest way to create custom exceptions is to extend one of the base
54+ exception classes like ` BadRequestException ` , ` UnprocessableEntityException ` , or
55+ ` ForbiddenException ` . These base classes implement ` ErrorProvider ` and use the
56+ ` JsonApiError ` trait internally to provide automatic error formatting.
57+
58+ For most cases, just extend a base exception and provide a message which will be
59+ used at the error ` detail ` . For more control, you can set the ` $this->error `
60+ array in your constructor, which will be merged with the defaults:
61+
62+ ``` php
63+ use Tobyz\JsonApiServer\Exception\BadRequestException;
64+
65+ class ProductOutOfStockException extends BadRequestException
66+ {
67+ public function __construct(string $productId)
68+ {
69+ parent::__construct("Product $productId is out of stock");
70+
71+ $this->error = [
72+ 'meta' => ['productId' => $productId],
73+ 'links' => [
74+ 'type' =>
75+ 'https://example.com/docs/errors#product_out_of_stock',
76+ ],
77+ ];
78+ }
79+ }
80+ ```
81+
82+ This automatically generates:
83+
84+ - ` code ` : ` product_out_of_stock ` (derived from class name)
85+ - ` title ` : ` Product Out Of Stock ` (derived from class name)
86+ - ` detail ` : ` Product ABC123 is out of stock ` (from constructor message)
87+ - ` status ` : ` 400 ` (inherited from BadRequestException)
88+
89+ #### Helper Methods
90+
91+ The ` JsonApiError ` trait also provides fluent helper methods for modifying the
92+ error object from the context in which it is thrown:
93+
94+ ``` php
95+ throw (new UnknownFieldException('email'))
96+ ->source(['pointer' => '/data/attributes/email'])
97+ ->meta(['suggestion' => 'Did you mean "emailAddress"?'])
98+ ->links(['about' => 'https://example.com/docs/fields']);
99+ ```
100+
101+ ## Multiple Errors
102+
103+ When multiple validation errors occur (e.g., multiple field validation
104+ failures), you can wrap them in ` JsonApiErrorsException ` .
105+
106+ ``` php
107+ use Tobyz\JsonApiServer\Exception\JsonApiErrorsException;
108+ use Tobyz\JsonApiServer\Exception\RequiredFieldException;
109+ use Tobyz\JsonApiServer\Exception\InvalidFieldValueException;
110+
111+ throw new JsonApiErrorsException([
112+ new RequiredFieldException(),
113+ new InvalidFieldValueException('Must be a valid email address'),
114+ ])->prependSource(['pointer' => '/data/attributes/email']);
115+ ```
116+
117+ This will return a JSON: API error response with multiple error objects:
118+
119+ ``` json
120+ {
121+ "errors" : [
122+ {
123+ "status" : " 422" ,
124+ "code" : " required_field" ,
125+ "title" : " Required Field" ,
126+ "detail" : " Field is required" ,
127+ "source" : { "pointer" : " /data/attributes/email" }
128+ },
129+ {
130+ "status" : " 422" ,
131+ "code" : " invalid_field_value" ,
132+ "title" : " Invalid Field Value" ,
133+ "detail" : " Must be a valid email address" ,
134+ "source" : { "pointer" : " /data/attributes/email" }
135+ }
136+ ]
137+ }
138+ ```
139+
140+ When ` JsonApiErrorsException ` contains multiple errors with different status
141+ codes, it automatically determines the most generally applicable HTTP error code
142+ to be used in the response.
143+
53144## Customizing Error Messages
54145
55- Many of the built-in error messages can be customized using the
56- [ localization system] ( localization.md ) . This allows you to provide localized or
57- custom error messages throughout your API.
146+ All built-in exceptions include sensible default English error messages, so they
147+ work out-of-the-box without any configuration. The ` code ` and ` title ` are
148+ automatically derived from the exception class name, and each exception provides
149+ a default ` detail ` message.
150+
151+ You can customize error messages for each exception using the ` errors() ` method
152+ on your ` JsonApi ` instance. You can override any part of the error object for
153+ any exception by providing exception class names as keys.
154+
155+ ``` php
156+ use Tobyz\JsonApiServer\Exception\MethodNotAllowedException;
157+ use Tobyz\JsonApiServer\Exception\ResourceNotFoundException;
158+
159+ $api->errors([
160+ MethodNotAllowedException::class => [
161+ 'title' => 'Not Allowed',
162+ 'detail' => 'The :method method is not allowed for this endpoint',
163+ ],
164+ ResourceNotFoundException::class => [
165+ 'title' => 'Not Found',
166+ 'detail' => 'Could not find :type resource with ID :id',
167+ ],
168+ ]);
169+ ```
170+
171+ ### Placeholder Replacement
172+
173+ The ` detail ` property supports placeholder replacement using the ` :placeholder `
174+ syntax. Placeholders are replaced with values found in the error object's ` meta `
175+ data:
176+
177+ ``` php
178+ ResourceNotFoundException::class => [
179+ 'detail' => 'Could not find :type resource with ID :id',
180+ ]
181+ ```
182+
183+ When a ` ResourceNotFoundException ` is thrown with ` meta ` containing
184+ ` ['type' => 'users', 'id' => '123'] ` , the detail becomes: "Could not find users
185+ resource with ID 123"
0 commit comments