Completed
Push — master ( c6554e...bc1f52 )
by Marcin
16s queued 11s
created

ResponseBuilder::make()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 9
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
c 9
b 0
f 0
nc 4
nop 9
dl 0
loc 18
ccs 10
cts 10
cp 1
crap 3
rs 9.9332

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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-2020 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 26
	protected function __construct(bool $success, int $api_code)
63
	{
64 26
		$this->success = $success;
65 26
		$this->api_code = $api_code;
66 26
	}
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 8
	public static function success($data = null, $api_code = null, array $placeholders = null,
87
	                               int $http_code = null, int $json_opts = null): HttpResponse
88
	{
89 8
		return ResponseBuilder::asSuccess($api_code)
90 8
			->withData($data)
91 6
			->withPlaceholders($placeholders)
92 6
			->withHttpCode($http_code)
93 6
			->withJsonOptions($json_opts)
94 6
			->build();
95
	}
96
97
	/**
98
	 * Builds error Response object. Supports optional arguments passed to Lang::get() if associated error
99
	 * message uses placeholders as well as return data payload
100
	 *
101
	 * @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
	 *                                         substitution or @null if none.
104
	 * @param object|array|null $data          Array of primitives and supported objects to be returned in 'data' node
105
	 *                                         of the JSON response, single supported object or @null if there's no
106
	 *                                         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
	 *                                         options or pass @null to use value from your config (or defaults).
111
	 *
112
	 * @return HttpResponse
113
	 */
114 1
	public static function error(int $api_code, array $placeholders = null, $data = null, int $http_code = null,
115
	                             int $json_opts = null): HttpResponse
116
	{
117 1
		return ResponseBuilder::asError($api_code)
118 1
			->withPlaceholders($placeholders)
119 1
			->withData($data)
120 1
			->withHttpCode($http_code)
121 1
			->withJsonOptions($json_opts)
122 1
			->build();
123
	}
124
125
	// -----------------------------------------------------------------------------------------------------------
126
127
	/**
128
	 * @param int|null $api_code
129
	 *
130
	 * @return \MarcinOrlowski\ResponseBuilder\ResponseBuilder
131
	 */
132 16
	public static function asSuccess(int $api_code = null): self
133
	{
134 16
		return new self(true, $api_code ?? BaseApiCodes::OK());
135
	}
136
137
	/**
138
	 * @param int $api_code
139
	 *
140
	 * @return \MarcinOrlowski\ResponseBuilder\ResponseBuilder
141
	 */
142 11
	public static function asError(int $api_code): self
143
	{
144 11
		$code_ok = BaseApiCodes::OK();
145 11
		if ($api_code !== $code_ok) {
146 10
			Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
147
		}
148 11
		if ($api_code === $code_ok) {
149 1
			throw new \InvalidArgumentException(
150 1
				"Error response cannot use api_code of value {$code_ok} which is reserved for OK");
151
		}
152
153 10
		return new self(false, $api_code);
154
	}
155
156
	/**
157
	 * @param int|null $http_code
158
	 *
159
	 * @return $this
160
	 */
161 16
	public function withHttpCode(int $http_code = null): self
162
	{
163 16
		Validator::assertIsType('http_code', $http_code, [Validator::TYPE_INTEGER,
164 16
		                                                  Validator::TYPE_NULL]);
165 16
		$this->http_code = $http_code;
166
167 16
		return $this;
168
	}
169
170
	/**
171
	 * @param null $data
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $data is correct as it would always require null to be passed?
Loading history...
172
	 *
173
	 * @return $this
174
	 */
175 18
	public function withData($data = null): self
176
	{
177 18
		Validator::assertIsType('data', $data, [Validator::TYPE_ARRAY,
178 18
		                                        Validator::TYPE_OBJECT,
179 18
		                                        Validator::TYPE_NULL]);
180 16
		$this->data = $data;
181
182 16
		return $this;
183
	}
184
185
	/**
186
	 * @param int|null $json_opts
187
	 *
188
	 * @return $this
189
	 */
190 7
	public function withJsonOptions(int $json_opts = null): self
191
	{
192 7
		Validator::assertIsType('json_opts', $json_opts, [Validator::TYPE_INTEGER,
193 7
		                                                  Validator::TYPE_NULL]);
194 7
		$this->json_opts = $json_opts;
195
196 7
		return $this;
197
	}
198
199
	/**
200
	 * @param array|null $debug_data
201
	 *
202
	 * @return $this
203
	 */
204 9
	public function withDebugData(array $debug_data = null): self
205
	{
206 9
		Validator::assertIsType('$debug_data', $debug_data, [Validator::TYPE_ARRAY,
207 9
		                                                     Validator::TYPE_NULL]);
208 9
		$this->debug_data = $debug_data;
209
210 9
		return $this;
211
	}
212
213
	/**
214
	 * @param string|null $msg
215
	 *
216
	 * @return $this
217
	 */
218 9
	public function withMessage(string $msg = null): self
219
	{
220 9
		Validator::assertIsType('message', $msg, [Validator::TYPE_STRING,
221 9
		                                          Validator::TYPE_NULL]);
222 9
		$this->message = $msg;
223
224 9
		return $this;
225
	}
226
227
	/**
228
	 * @param array|null $placeholders
229
	 *
230
	 * @return $this
231
	 */
232 7
	public function withPlaceholders(array $placeholders = null): self
233
	{
234 7
		$this->placeholders = $placeholders;
235
236 7
		return $this;
237
	}
238
239
	/**
240
	 * @param array|null $http_headers
241
	 *
242
	 * @return $this
243
	 */
244 1
	public function withHttpHeaders(array $http_headers = null): self
245
	{
246 1
		$this->http_headers = $http_headers ?? [];
247
248 1
		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 18
	public function build(): HttpResponse
259
	{
260 18
		$api_code = $this->api_code;
261 18
		Validator::assertIsInt('api_code', $api_code);
262
263 18
		$msg_or_api_code = $this->message ?? $api_code;
264 18
		$http_headers = $this->http_headers ?? [];
265
266 18
		if ($this->success) {
267 8
			$api_code = $api_code ?? BaseApiCodes::OK();
268 8
			$http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_OK;
269
270 8
			Validator::assertOkHttpCode($http_code);
271
272 8
			$result = $this->make($this->success, $api_code, $msg_or_api_code, $this->data, $http_code,
273 8
				$this->placeholders, $http_headers, $this->json_opts);
274
		} else {
275 10
			$http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_ERROR;
276
277 10
			Validator::assertErrorHttpCode($http_code);
278
279 10
			$result = $this->make(false, $api_code, $msg_or_api_code, $this->data, $http_code, $this->placeholders,
280 10
				$this->http_headers, $this->json_opts, $this->debug_data);
281
		}
282
283 17
		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
	 * @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
	 * @param array|null        $http_headers    Optional HTTP headers to be returned in the response.
296
	 * @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
	 */
307 24
	protected function make(bool $success, int $api_code, $msg_or_api_code, $data = null,
308
	                        int $http_code = null, array $placeholders = null, array $http_headers = null,
309
	                        int $json_opts = null, array $debug_data = null): HttpResponse
310
	{
311 24
		$http_headers = $http_headers ?? [];
312 24
		$http_code = $http_code ?? ($success ? ResponseBuilder::DEFAULT_HTTP_CODE_OK : ResponseBuilder::DEFAULT_HTTP_CODE_ERROR);
313 24
		$json_opts = $json_opts ?? Config::get(ResponseBuilder::CONF_KEY_ENCODING_OPTIONS, ResponseBuilder::DEFAULT_ENCODING_OPTIONS);
314
315 24
		Validator::assertIsInt('encoding_options', $json_opts);
316
317 23
		Validator::assertIsInt('api_code', $api_code);
318 23
		if (!BaseApiCodes::isCodeValid($api_code)) {
319 1
			Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
320
		}
321
322 22
		return Response::json(
323 22
			$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
	 * 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
	 * @param string|integer    $msg_or_api_code Message string or valid API code to get message for.
336
	 * @param array|null        $placeholders    Placeholders passed to Lang::get() for message placeholders
337
	 *                                           substitution or @null if none.
338
	 * @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
	 *
341
	 * @return array response ready to be encoded as json and sent back to client
342
	 *
343
	 * @throws \RuntimeException in case of missing or invalid "classes" mapping configuration
344
	 *
345
	 * @noinspection PhpTooManyParametersInspection
346
	 */
347 22
	protected function buildResponse(bool $success, int $api_code,
348
	                                 $msg_or_api_code, array $placeholders = null,
349
	                                 $data = null, array $debug_data = null): array
350
	{
351
		// ensure $data is either @null, array or object of class with configured mapping.
352 22
		$data = (new Converter())->convert($data);
353 21
		if ($data !== null && !\is_object($data)) {
354
			// ensure we get object in final JSON structure in data node
355 6
			$data = (object)$data;
356
		}
357
358
		// get human readable message for API code or use message string (if given instead of API code)
359 21
		if (\is_int($msg_or_api_code)) {
360 10
			$message = $this->getMessageForApiCode($success, $msg_or_api_code, $placeholders);
361
		} else {
362 11
			Validator::assertIsString('message', $msg_or_api_code);
363 9
			$message = $msg_or_api_code;
364
		}
365
366
		/** @noinspection PhpUndefinedClassInspection */
367
		$response = [
368 19
			ResponseBuilder::KEY_SUCCESS => $success,
369 19
			ResponseBuilder::KEY_CODE    => $api_code,
370 19
			ResponseBuilder::KEY_LOCALE  => \App::getLocale(),
371 19
			ResponseBuilder::KEY_MESSAGE => $message,
372 19
			ResponseBuilder::KEY_DATA    => $data,
373
		];
374
375 19
		if ($debug_data !== null) {
376 2
			$debug_key = Config::get(ResponseBuilder::CONF_KEY_DEBUG_DEBUG_KEY, ResponseBuilder::KEY_DEBUG);
377 2
			$response[ $debug_key ] = $debug_data;
378
		}
379
380 19
		return $response;
381
	}
382
383
	/**
384
	 * If $msg_or_api_code is integer value, returns human readable message associated with that code (with
385
	 * fallback to built-in default string if no api code mapping is set. If $msg_or_api_code is a string,
386
	 * returns it unaltered.
387
	 *
388
	 * @param boolean    $success      @true if response reports successful operation, @false otherwise.
389
	 * @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
	 *
393
	 * @return string
394
	 */
395 10
	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 10
		$key = BaseApiCodes::getCodeMessageKey($api_code);
400 10
		if ($key === null) {
401
			// nope, let's get the default one instead, based of
402
			$fallback_code = $success ? BaseApiCodes::OK() : BaseApiCodes::NO_ERROR_MESSAGE();
403
			$key = BaseApiCodes::getCodeMessageKey($fallback_code);
404
		}
405
406 10
		$placeholders = $placeholders ?? [];
407 10
		if (!\array_key_exists('api_code', $placeholders)) {
408 10
			$placeholders['api_code'] = $api_code;
409
		}
410
411 10
		return \Lang::get($key, $placeholders);
412
	}
413
}
414