1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace MarcinOrlowski\ResponseBuilder; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Laravel API Response Builder |
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 Illuminate\Support\Facades\Config; |
18
|
|
|
use Illuminate\Support\Facades\Response; |
19
|
|
|
use Symfony\Component\HttpFoundation\Response as HttpResponse; |
20
|
|
|
|
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Builds standardized HttpResponse response object |
24
|
|
|
*/ |
25
|
|
|
class ResponseBuilder extends ResponseBuilderBase |
26
|
|
|
{ |
27
|
|
|
/** @var bool */ |
28
|
|
|
protected $success = false; |
29
|
|
|
|
30
|
|
|
/** @var int */ |
31
|
|
|
protected $api_code; |
32
|
|
|
|
33
|
|
|
/** @var int|null */ |
34
|
|
|
protected $http_code = null; |
35
|
|
|
|
36
|
|
|
/** @var mixed */ |
37
|
|
|
protected $data = null; |
38
|
|
|
|
39
|
|
|
/** @var string */ |
40
|
|
|
protected $message = null; |
41
|
|
|
|
42
|
|
|
/** @var array */ |
43
|
|
|
protected $placeholders = []; |
44
|
|
|
|
45
|
|
|
/** @var int|null */ |
46
|
|
|
protected $json_opts = null; |
47
|
|
|
|
48
|
|
|
/** @var array */ |
49
|
|
|
protected $debug_data = []; |
50
|
|
|
|
51
|
|
|
/** @var array */ |
52
|
|
|
protected $http_headers = []; |
53
|
|
|
|
54
|
|
|
// ----------------------------------------------------------------------------------------------------------- |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Private constructor. Use asSuccess() and asError() static methods to obtain instance of Builder. |
58
|
|
|
* |
59
|
|
|
* @param bool $success |
60
|
|
|
* @param int $api_code |
61
|
|
|
*/ |
62
|
|
|
protected function __construct(bool $success, int $api_code) |
63
|
|
|
{ |
64
|
|
|
$this->success = $success; |
65
|
|
|
$this->api_code = $api_code; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
// ----------------------------------------------------------------------------------------------------------- |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Returns success |
72
|
|
|
* |
73
|
|
|
* @param object|array|null $data Array of primitives and supported objects to be returned in 'data' node |
74
|
|
|
* of the JSON response, single supported object or @null if there's no |
75
|
|
|
* to be returned. |
76
|
|
|
* @param integer|null $api_code API code to be returned or @null to use value of BaseApiCodes::OK(). |
77
|
|
|
* @param array|null $placeholders Placeholders passed to Lang::get() for message placeholders |
78
|
|
|
* substitution or @null if none. |
79
|
|
|
* @param integer|null $http_code HTTP code to be used for HttpResponse sent or @null |
80
|
|
|
* for default DEFAULT_HTTP_CODE_OK. |
81
|
|
|
* @param integer|null $json_opts See http://php.net/manual/en/function.json-encode.php for supported |
82
|
|
|
* options or pass @null to use value from your config (or defaults). |
83
|
|
|
* |
84
|
|
|
* @return HttpResponse |
85
|
|
|
*/ |
86
|
|
|
public static function success($data = null, $api_code = null, array $placeholders = null, |
87
|
|
|
int $http_code = null, int $json_opts = null): HttpResponse |
88
|
|
|
{ |
89
|
|
|
return ResponseBuilder::asSuccess($api_code) |
90
|
|
|
->withData($data) |
91
|
|
|
->withPlaceholders($placeholders) |
92
|
|
|
->withHttpCode($http_code) |
93
|
|
|
->withJsonOptions($json_opts) |
94
|
|
|
->build(); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Builds error Response object. Supports optional arguments passed to Lang::get() if associated error |
99
|
4 |
|
* message uses placeholders as well as return data payload |
100
|
|
|
* |
101
|
4 |
|
* @param integer $api_code Your API code to be returned with the response object. |
102
|
|
|
* @param array|null $placeholders Placeholders passed to Lang::get() for message placeholders |
103
|
4 |
|
* substitution or @null if none. |
104
|
3 |
|
* @param object|array|null $data Array of primitives and supported objects to be returned in 'data' node |
105
|
2 |
|
* of the JSON response, single supported object or @null if there's no |
106
|
2 |
|
* to be returned. |
107
|
|
|
* @param integer|null $http_code HTTP code to be used for HttpResponse sent or @null |
108
|
|
|
* for default DEFAULT_HTTP_CODE_ERROR. |
109
|
|
|
* @param integer|null $json_opts See http://php.net/manual/en/function.json-encode.php for supported |
110
|
1 |
|
* options or pass @null to use value from your config (or defaults). |
111
|
1 |
|
* |
112
|
|
|
* @return HttpResponse |
113
|
1 |
|
*/ |
114
|
1 |
|
public static function error(int $api_code, array $placeholders = null, $data = null, int $http_code = null, |
115
|
1 |
|
int $json_opts = null): HttpResponse |
116
|
1 |
|
{ |
117
|
|
|
return ResponseBuilder::asError($api_code) |
118
|
|
|
->withPlaceholders($placeholders) |
119
|
|
|
->withData($data) |
120
|
|
|
->withHttpCode($http_code) |
121
|
1 |
|
->withJsonOptions($json_opts) |
122
|
|
|
->build(); |
123
|
|
|
} |
124
|
1 |
|
|
125
|
|
|
// ----------------------------------------------------------------------------------------------------------- |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @param int|null $api_code |
129
|
|
|
* |
130
|
|
|
* @return \MarcinOrlowski\ResponseBuilder\ResponseBuilder |
131
|
|
|
*/ |
132
|
|
|
public static function asSuccess(int $api_code = null): self |
133
|
|
|
{ |
134
|
|
|
return new self(true, $api_code ?? BaseApiCodes::OK()); |
|
|
|
|
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @param int $api_code |
139
|
12 |
|
* |
140
|
|
|
* @return \MarcinOrlowski\ResponseBuilder\ResponseBuilder |
141
|
|
|
*/ |
142
|
12 |
|
public static function asError(int $api_code): self |
143
|
|
|
{ |
144
|
|
|
$code_ok = BaseApiCodes::OK(); |
|
|
|
|
145
|
|
|
if ($api_code !== $code_ok) { |
146
|
|
|
Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode()); |
147
|
|
|
} |
148
|
|
|
if ($api_code === $code_ok) { |
149
|
|
|
throw new \InvalidArgumentException( |
150
|
|
|
"Error response cannot use api_code of value {$code_ok} which is reserved for OK"); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
return new self(false, $api_code); |
154
|
1 |
|
} |
155
|
|
|
|
156
|
1 |
|
/** |
157
|
|
|
* @param int|null $http_code |
158
|
|
|
* |
159
|
|
|
* @return $this |
160
|
|
|
*/ |
161
|
|
|
public function withHttpCode(int $http_code = null): self |
162
|
|
|
{ |
163
|
|
|
Validator::assertIsType('http_code', $http_code, [Validator::TYPE_INTEGER, |
164
|
|
|
Validator::TYPE_NULL]); |
165
|
|
|
$this->http_code = $http_code; |
166
|
|
|
|
167
|
4 |
|
return $this; |
168
|
|
|
} |
169
|
4 |
|
|
170
|
|
|
/** |
171
|
|
|
* @param null $data |
|
|
|
|
172
|
|
|
* |
173
|
|
|
* @return $this |
174
|
|
|
*/ |
175
|
|
|
public function withData($data = null): self |
176
|
|
|
{ |
177
|
|
|
Validator::assertIsType('data', $data, [Validator::TYPE_ARRAY, |
178
|
|
|
Validator::TYPE_OBJECT, |
179
|
|
|
Validator::TYPE_NULL]); |
180
|
|
|
$this->data = $data; |
181
|
|
|
|
182
|
|
|
return $this; |
183
|
|
|
} |
184
|
16 |
|
|
185
|
|
|
/** |
186
|
|
|
* @param int|null $json_opts |
187
|
16 |
|
* |
188
|
16 |
|
* @return $this |
189
|
|
|
*/ |
190
|
16 |
|
public function withJsonOptions(int $json_opts = null): self |
191
|
16 |
|
{ |
192
|
16 |
|
Validator::assertIsType('json_opts', $json_opts, [Validator::TYPE_INTEGER, |
193
|
|
|
Validator::TYPE_NULL]); |
194
|
14 |
|
$this->json_opts = $json_opts; |
195
|
|
|
|
196
|
|
|
return $this; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* @param array|null $debug_data |
201
|
|
|
* |
202
|
|
|
* @return $this |
203
|
|
|
*/ |
204
|
|
|
public function withDebugData(array $debug_data = null): self |
205
|
|
|
{ |
206
|
|
|
Validator::assertIsType('$debug_data', $debug_data, [Validator::TYPE_ARRAY, |
207
|
|
|
Validator::TYPE_NULL]); |
208
|
|
|
$this->debug_data = $debug_data; |
209
|
|
|
|
210
|
4 |
|
return $this; |
211
|
|
|
} |
212
|
|
|
|
213
|
4 |
|
/** |
214
|
|
|
* @param string|null $msg |
215
|
|
|
* |
216
|
|
|
* @return $this |
217
|
|
|
*/ |
218
|
|
|
public function withMessage(string $msg = null): self |
219
|
|
|
{ |
220
|
|
|
Validator::assertIsType('message', $msg, [Validator::TYPE_STRING, |
221
|
|
|
Validator::TYPE_NULL]); |
222
|
|
|
$this->message = $msg; |
223
|
|
|
|
224
|
|
|
return $this; |
225
|
1 |
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
1 |
|
* @param array|null $placeholders |
229
|
|
|
* |
230
|
|
|
* @return $this |
231
|
|
|
*/ |
232
|
|
|
public function withPlaceholders(array $placeholders = null): self |
233
|
|
|
{ |
234
|
|
|
$this->placeholders = $placeholders; |
235
|
|
|
|
236
|
|
|
return $this; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @param array|null $http_headers |
241
|
|
|
* |
242
|
|
|
* @return $this |
243
|
1 |
|
*/ |
244
|
|
|
public function withHttpHeaders(array $http_headers = null): self |
245
|
|
|
{ |
246
|
1 |
|
$this->http_headers = $http_headers ?? []; |
247
|
|
|
|
248
|
|
|
return $this; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* Builds and returns final HttpResponse. It's safe to call build() as many times as needed, as no |
253
|
|
|
* internal state is changed. It's also safe to alter any parameter set previously and call build() |
254
|
|
|
* again to get new response object that includes new changes. |
255
|
|
|
* |
256
|
|
|
* @return \Symfony\Component\HttpFoundation\Response |
257
|
|
|
*/ |
258
|
1 |
|
public function build(): HttpResponse |
259
|
|
|
{ |
260
|
1 |
|
$api_code = $this->api_code; |
261
|
|
|
Validator::assertIsInt('api_code', $api_code); |
262
|
|
|
|
263
|
|
|
$msg_or_api_code = $this->message ?? $api_code; |
264
|
|
|
$http_headers = $this->http_headers ?? []; |
265
|
|
|
|
266
|
|
|
if ($this->success) { |
267
|
|
|
$api_code = $api_code ?? BaseApiCodes::OK(); |
|
|
|
|
268
|
|
|
$http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_OK; |
269
|
|
|
|
270
|
|
|
Validator::assertOkHttpCode($http_code); |
271
|
|
|
|
272
|
|
|
$result = $this->make($this->success, $api_code, $msg_or_api_code, $this->data, $http_code, |
273
|
1 |
|
$this->placeholders, $http_headers, $this->json_opts); |
274
|
|
|
} else { |
275
|
|
|
$http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_ERROR; |
276
|
1 |
|
|
277
|
1 |
|
Validator::assertErrorHttpCode($http_code); |
278
|
|
|
|
279
|
|
|
$result = $this->make(false, $api_code, $msg_or_api_code, $this->data, $http_code, $this->placeholders, |
280
|
|
|
$this->http_headers, $this->json_opts, $this->debug_data); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
return $result; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* @param boolean $success @true if response reports successful operation, @false otherwise. |
288
|
|
|
* @param integer $api_code Your API code to be returned with the response object. |
289
|
|
|
* @param string|integer $msg_or_api_code message string or valid API code to get message for |
290
|
|
|
* @param object|array|null $data optional additional data to be included in response object |
291
|
6 |
|
* @param integer|null $http_code HTTP code for the HttpResponse or @null for either DEFAULT_HTTP_CODE_OK |
292
|
|
|
* or DEFAULT_HTTP_CODE_ERROR depending on the $success. |
293
|
|
|
* @param array|null $placeholders Placeholders passed to Lang::get() for message placeholders |
294
|
|
|
* substitution or @null if none. |
295
|
6 |
|
* @param array|null $http_headers Optional HTTP headers to be returned in the response. |
296
|
6 |
|
* @param integer|null $json_opts See http://php.net/manual/en/function.json-encode.php for supported |
297
|
|
|
* options or pass @null to use value from your config (or defaults). |
298
|
|
|
* @param array|null $debug_data Optional debug data array to be added to returned JSON. |
299
|
|
|
* |
300
|
|
|
* @return HttpResponse |
301
|
|
|
* |
302
|
|
|
* @throws \InvalidArgumentException If $api_code is neither a string nor valid integer code. |
303
|
|
|
* @throws \InvalidArgumentException if $data is an object of class that is not configured in "classes" mapping. |
304
|
|
|
* |
305
|
|
|
* @noinspection PhpTooManyParametersInspection |
306
|
1 |
|
*/ |
307
|
|
|
protected function make(bool $success, int $api_code, $msg_or_api_code, $data = null, |
308
|
1 |
|
int $http_code = null, array $placeholders = null, array $http_headers = null, |
309
|
|
|
int $json_opts = null, array $debug_data = null): HttpResponse |
310
|
|
|
{ |
311
|
|
|
$http_headers = $http_headers ?? []; |
312
|
|
|
$http_code = $http_code ?? ($success ? ResponseBuilder::DEFAULT_HTTP_CODE_OK : ResponseBuilder::DEFAULT_HTTP_CODE_ERROR); |
313
|
|
|
$json_opts = $json_opts ?? Config::get(ResponseBuilder::CONF_KEY_ENCODING_OPTIONS, ResponseBuilder::DEFAULT_ENCODING_OPTIONS); |
314
|
|
|
|
315
|
|
|
Validator::assertIsInt('encoding_options', $json_opts); |
316
|
|
|
|
317
|
|
|
Validator::assertIsInt('api_code', $api_code); |
318
|
|
|
if (!BaseApiCodes::isCodeValid($api_code)) { |
319
|
|
|
Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode()); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
return Response::json( |
323
|
|
|
$this->buildResponse($success, $api_code, $msg_or_api_code, $placeholders, $data, $debug_data), |
324
|
|
|
$http_code, $http_headers, $json_opts); |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Creates standardised API response array. This is final method called in the whole pipeline before we |
329
|
|
|
* return final JSON back to client. If you want to manipulate your response, this is the place to do that. |
330
|
|
|
* If you set APP_DEBUG to true, 'code_hex' field will be additionally added to reported JSON for easier |
331
|
18 |
|
* manual debugging. |
332
|
|
|
* |
333
|
|
|
* @param boolean $success @true if response reports successful operation, @false otherwise. |
334
|
|
|
* @param integer $api_code Your API code to be returned with the response object. |
335
|
18 |
|
* @param string|integer $msg_or_api_code Message string or valid API code to get message for. |
336
|
18 |
|
* @param array|null $placeholders Placeholders passed to Lang::get() for message placeholders |
337
|
|
|
* substitution or @null if none. |
338
|
18 |
|
* @param object|array|null $data API response data if any |
339
|
|
|
* @param array|null $debug_data optional debug data array to be added to returned JSON. |
340
|
18 |
|
* |
341
|
18 |
|
* @return array response ready to be encoded as json and sent back to client |
342
|
16 |
|
* |
343
|
|
|
* @throws \RuntimeException in case of missing or invalid "classes" mapping configuration |
344
|
18 |
|
* |
345
|
2 |
|
* @noinspection PhpTooManyParametersInspection |
346
|
|
|
*/ |
347
|
|
|
protected function buildResponse(bool $success, int $api_code, |
348
|
16 |
|
$msg_or_api_code, array $placeholders = null, |
349
|
16 |
|
$data = null, array $debug_data = null): array |
350
|
|
|
{ |
351
|
15 |
|
// ensure $data is either @null, array or object of class with configured mapping. |
352
|
|
|
$data = (new Converter())->convert($data); |
353
|
15 |
|
if ($data !== null && !is_object($data)) { |
354
|
15 |
|
// ensure we get object in final JSON structure in data node |
355
|
|
|
$data = (object)$data; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
// get human readable message for API code or use message string (if given instead of API code) |
359
|
|
|
if (is_int($msg_or_api_code)) { |
360
|
|
|
$message = $this->getMessageForApiCode($success, $msg_or_api_code, $placeholders); |
361
|
|
|
} else { |
362
|
|
|
Validator::assertIsString('message', $msg_or_api_code); |
363
|
|
|
$message = $msg_or_api_code; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** @noinspection PhpUndefinedClassInspection */ |
367
|
|
|
$response = [ |
368
|
|
|
ResponseBuilder::KEY_SUCCESS => $success, |
369
|
|
|
ResponseBuilder::KEY_CODE => $api_code, |
370
|
|
|
ResponseBuilder::KEY_LOCALE => \App::getLocale(), |
371
|
|
|
ResponseBuilder::KEY_MESSAGE => $message, |
372
|
|
|
ResponseBuilder::KEY_DATA => $data, |
373
|
|
|
]; |
374
|
|
|
|
375
|
35 |
|
if ($debug_data !== null) { |
376
|
|
|
$debug_key = Config::get(ResponseBuilder::CONF_KEY_DEBUG_DEBUG_KEY, ResponseBuilder::KEY_DEBUG); |
377
|
|
|
$response[ $debug_key ] = $debug_data; |
378
|
|
|
} |
379
|
35 |
|
|
380
|
35 |
|
return $response; |
381
|
35 |
|
} |
382
|
|
|
|
383
|
35 |
|
/** |
384
|
|
|
* If $msg_or_api_code is integer value, returns human readable message associated with that code (with |
385
|
34 |
|
* fallback to built-in default string if no api code mapping is set. If $msg_or_api_code is a string, |
386
|
34 |
|
* returns it unaltered. |
387
|
1 |
|
* |
388
|
1 |
|
* @param boolean $success @true if response reports successful operation, @false otherwise. |
389
|
1 |
|
* @param integer $api_code Your API code to be returned with the response object. |
390
|
|
|
* @param array|null $placeholders Placeholders passed to Lang::get() for message placeholders |
391
|
|
|
* substitution or @null if none. |
392
|
33 |
|
* |
393
|
2 |
|
* @return string |
394
|
2 |
|
*/ |
395
|
2 |
|
protected function getMessageForApiCode(bool $success, int $api_code, array $placeholders = null): string |
396
|
|
|
{ |
397
|
|
|
// We got integer value here not a message string, so we need to check if we have the mapping for |
398
|
|
|
// this string already configured. |
399
|
|
|
$key = BaseApiCodes::getCodeMessageKey($api_code); |
400
|
|
|
if ($key === null) { |
401
|
31 |
|
// nope, let's get the default one instead, based of |
402
|
23 |
|
$fallback_code = $success ? BaseApiCodes::OK() : BaseApiCodes::NO_ERROR_MESSAGE(); |
|
|
|
|
403
|
23 |
|
$key = BaseApiCodes::getCodeMessageKey($fallback_code); |
404
|
|
|
} |
405
|
2 |
|
|
406
|
|
|
$placeholders = $placeholders ?? []; |
407
|
|
|
if (!array_key_exists('api_code', $placeholders)) { |
408
|
23 |
|
$placeholders['api_code'] = $api_code; |
409
|
23 |
|
} |
410
|
|
|
|
411
|
|
|
return \Lang::get($key, $placeholders); |
412
|
31 |
|
} |
413
|
|
|
} |
414
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.