ResponseBuilder::make()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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

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