Completed
Push — master ( 277a70...42ae0f )
by Marcin
24s 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
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
	 */
63 31
	protected function __construct(bool $success, int $api_code)
64
	{
65 31
		$this->success = $success;
66 31
		$this->api_code = $api_code;
67 31
	}
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
	 */
87 11
	public static function success($data = null, $api_code = null, array $placeholders = null,
88
	                               int $http_code = null, int $json_opts = null): HttpResponse
89
	{
90 11
		return static::asSuccess($api_code)
91 11
			->withData($data)
92 11
			->withPlaceholders($placeholders)
93 11
			->withHttpCode($http_code)
94 11
			->withJsonOptions($json_opts)
95 11
			->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
	 */
115 1
	public static function error(int $api_code, array $placeholders = null, $data = null, int $http_code = null,
116
	                             int $json_opts = null): HttpResponse
117
	{
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 1
			->build();
124
	}
125
126
	// -----------------------------------------------------------------------------------------------------------
127
128
	/**
129
	 * @param int|null $api_code
130
	 *
131
	 * @return \MarcinOrlowski\ResponseBuilder\ResponseBuilder
132
	 */
133 20
	public static function asSuccess(int $api_code = null): self
134
	{
135 20
		return new static(true, $api_code ?? BaseApiCodes::OK());
136
	}
137
138
	/**
139
	 * @param int $api_code
140
	 *
141
	 * @return \MarcinOrlowski\ResponseBuilder\ResponseBuilder
142
	 */
143 12
	public static function asError(int $api_code): self
144
	{
145 12
		$code_ok = BaseApiCodes::OK();
146 12
		if ($api_code !== $code_ok) {
147 11
			Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
148
		}
149 12
		if ($api_code === $code_ok) {
150 1
			throw new \OutOfBoundsException(
151 1
				"Error response cannot use api_code of value {$code_ok} which is reserved for OK.");
152
		}
153
154 11
		return new static(false, $api_code);
155
	}
156
157
	/**
158
	 * @param int|null $http_code
159
	 *
160
	 * @return $this
161
	 */
162 21
	public function withHttpCode(int $http_code = null): self
163
	{
164 21
		Validator::assertIsType('http_code', $http_code, [
165 21
			Type::INTEGER,
166 21
			Type::NULL]);
167 21
		$this->http_code = $http_code;
168
169 21
		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
	 * @return $this
176
	 */
177 21
	public function withData($data = null): self
178
	{
179 21
		Validator::assertIsType('data', $data, [
180 21
			Type::ARRAY,
181 21
			Type::BOOLEAN,
182 21
			Type::INTEGER,
183 21
			Type::NULL,
184 21
			Type::OBJECT,
185 21
			Type::STRING,
186
		]);
187 21
		$this->data = $data;
188
189 21
		return $this;
190
	}
191
192
	/**
193
	 * @param int|null $json_opts
194
	 *
195
	 * @return $this
196
	 */
197 12
	public function withJsonOptions(int $json_opts = null): self
198
	{
199 12
		Validator::assertIsType('json_opts', $json_opts, [Type::INTEGER,
200 12
		                                                  Type::NULL]);
201 12
		$this->json_opts = $json_opts;
202
203 12
		return $this;
204
	}
205
206
	/**
207
	 * @param array|null $debug_data
208
	 *
209
	 * @return $this
210
	 */
211 9
	public function withDebugData(array $debug_data = null): self
212
	{
213 9
		Validator::assertIsType('$debug_data', $debug_data, [Type::ARRAY,
214 9
		                                                     Type::NULL]);
215 9
		$this->debug_data = $debug_data;
216
217 9
		return $this;
218
	}
219
220
	/**
221
	 * @param string|null $msg
222
	 *
223
	 * @return $this
224
	 */
225 9
	public function withMessage(string $msg = null): self
226
	{
227 9
		Validator::assertIsType('message', $msg, [Type::STRING,
228 9
		                                          Type::NULL]);
229 9
		$this->message = $msg;
230
231 9
		return $this;
232
	}
233
234
	/**
235
	 * @param array|null $placeholders
236
	 *
237
	 * @return $this
238
	 */
239 12
	public function withPlaceholders(array $placeholders = null): self
240
	{
241 12
		$this->placeholders = $placeholders;
242
243 12
		return $this;
244
	}
245
246
	/**
247
	 * @param array|null $http_headers
248
	 *
249
	 * @return $this
250
	 */
251 1
	public function withHttpHeaders(array $http_headers = null): self
252
	{
253 1
		$this->http_headers = $http_headers ?? [];
254
255 1
		return $this;
256
	}
257
258
	/**
259
	 * Builds and returns final HttpResponse. It's safe to call build() as many times as needed, as no
260
	 * internal state is changed. It's also safe to alter any parameter set previously and call build()
261
	 * again to get new response object that includes new changes.
262
	 *
263
	 * @return \Symfony\Component\HttpFoundation\Response
264
	 */
265 23
	public function build(): HttpResponse
266
	{
267 23
		$api_code = $this->api_code;
268 23
		Validator::assertIsInt('api_code', $api_code);
269
270 23
		$msg_or_api_code = $this->message ?? $api_code;
271 23
		$http_headers = $this->http_headers ?? [];
272
273 23
		if ($this->success) {
274 13
			$api_code = $api_code ?? BaseApiCodes::OK();
275 13
			$http_code = $this->http_code ?? RB::DEFAULT_HTTP_CODE_OK;
276
277 13
			Validator::assertOkHttpCode($http_code);
278
279 13
			$result = $this->make($this->success, $api_code, $msg_or_api_code, $this->data, $http_code,
280 13
				$this->placeholders, $http_headers, $this->json_opts);
281
		} else {
282 10
			$http_code = $this->http_code ?? RB::DEFAULT_HTTP_CODE_ERROR;
283
284 10
			Validator::assertErrorHttpCode($http_code);
285
286 10
			$result = $this->make(false, $api_code, $msg_or_api_code, $this->data, $http_code, $this->placeholders,
287 10
				$this->http_headers, $this->json_opts, $this->debug_data);
288
		}
289
290 23
		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
	 * @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
	 *
312
	 * @noinspection PhpTooManyParametersInspection
313
	 */
314 29
	protected function make(bool $success, int $api_code, $msg_or_api_code, $data = null,
315
	                        int $http_code = null, array $placeholders = null, array $http_headers = null,
316
	                        int $json_opts = null, array $debug_data = null): HttpResponse
317
	{
318 29
		$http_headers = $http_headers ?? [];
319 29
		$http_code = $http_code ?? ($success ? RB::DEFAULT_HTTP_CODE_OK : RB::DEFAULT_HTTP_CODE_ERROR);
320 29
		$json_opts = $json_opts ?? Config::get(RB::CONF_KEY_ENCODING_OPTIONS, RB::DEFAULT_ENCODING_OPTIONS);
321
322 29
		Validator::assertIsInt('encoding_options', $json_opts);
323
324 28
		Validator::assertIsInt('api_code', $api_code);
325 28
		if (!BaseApiCodes::isCodeValid($api_code)) {
326 1
			Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
327
		}
328
329 27
		return Response::json(
330 27
			$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
	 *
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
	 * @noinspection PhpTooManyParametersInspection
353
	 */
354 26
	protected function buildResponse(bool $success, int $api_code,
355
	                                 $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 26
		$data = (new Converter())->convert($data);
360
//		if ($data !== null && !\is_object($data)) {
361
//			// ensure we get object in final JSON structure in data node
362
//			$data = (object)$data;
363
//		}
364
365
		// get human readable message for API code or use message string (if given instead of API code)
366 26
		if (\is_int($msg_or_api_code)) {
367 15
			$message = $this->getMessageForApiCode($success, $msg_or_api_code, $placeholders);
368
		} else {
369 11
			Validator::assertIsString('message', $msg_or_api_code);
370 9
			$message = $msg_or_api_code;
371
		}
372
373
		/** @noinspection PhpUndefinedClassInspection */
374
		$response = [
375 24
			RB::KEY_SUCCESS => $success,
376 24
			RB::KEY_CODE    => $api_code,
377 24
			RB::KEY_LOCALE  => \App::getLocale(),
378 24
			RB::KEY_MESSAGE => $message,
379 24
			RB::KEY_DATA    => $data,
380
		];
381
382 24
		if ($debug_data !== null) {
383 2
			$debug_key = Config::get(RB::CONF_KEY_DEBUG_DEBUG_KEY, RB::KEY_DEBUG);
384 2
			$response[ $debug_key ] = $debug_data;
385
		}
386
387 24
		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
	 * @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
	 *
400
	 * @return string
401
	 */
402 15
	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 15
		$key = BaseApiCodes::getCodeMessageKey($api_code);
407 15
		if ($key === null) {
408
			// 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
		}
412
413 15
		$placeholders = $placeholders ?? [];
414 15
		if (!\array_key_exists('api_code', $placeholders)) {
415 15
			$placeholders['api_code'] = $api_code;
416
		}
417
418 15
		return \Lang::get($key, $placeholders);
419
	}
420
}
421