Passed
Push — master ( 7b9d4f...6fbff1 )
by Marcin
02:19
created

ResponseBuilder::buildErrorResponse()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

Changes 12
Bugs 0 Features 0
Metric Value
cc 3
eloc 13
c 12
b 0
f 0
nc 4
nop 8
dl 0
loc 24
ccs 14
cts 14
cp 1
crap 3
rs 9.8333

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-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 46
    protected function __construct(bool $success, int $api_code)
63
    {
64 46
        $this->success = $success;
65 46
        $this->api_code = $api_code;
66 46
    }
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 6
    public static function success($data = null, $api_code = null, array $placeholders = null,
87
                                   int $http_code = null, int $json_opts = null): HttpResponse
88
    {
89 6
        return ResponseBuilder::asSuccess($api_code)
90 6
            ->withData($data)
91 4
            ->withPlaceholders($placeholders)
92 4
            ->withHttpCode($http_code)
93 4
            ->withJsonOptions($json_opts)
94 4
            ->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 26
    public static function asSuccess(int $api_code = null): self
133
    {
134 26
        return new self(true, $api_code ?? BaseApiCodes::OK());
0 ignored issues
show
Deprecated Code introduced by
The function MarcinOrlowski\ResponseBuilder\BaseApiCodes::OK() has been deprecated: Configure Exception Handler to use your own API code. This method will be removed in v8. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

134
        return new self(true, $api_code ?? /** @scrutinizer ignore-deprecated */ BaseApiCodes::OK());

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.

Loading history...
135
    }
136
137
    /**
138
     * @param int $api_code
139
     *
140
     * @return \MarcinOrlowski\ResponseBuilder\ResponseBuilder
141
     */
142 22
    public static function asError(int $api_code): self
143
    {
144 22
        $code_ok = BaseApiCodes::OK();
0 ignored issues
show
Deprecated Code introduced by
The function MarcinOrlowski\ResponseBuilder\BaseApiCodes::OK() has been deprecated: Configure Exception Handler to use your own API code. This method will be removed in v8. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

144
        $code_ok = /** @scrutinizer ignore-deprecated */ BaseApiCodes::OK();

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.

Loading history...
145 22
        if ($api_code !== $code_ok) {
146 20
            Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
147
        }
148 22
        if ($api_code === $code_ok) {
149 2
            throw new \InvalidArgumentException(
150 2
                "Error response cannot use api_code of value {$code_ok} which is reserved for OK");
151
        }
152
153 20
        return new self(false, $api_code);
154
    }
155
156
    /**
157
     * @param int|null $http_code
158
     *
159
     * @return $this
160
     */
161 35
    public function withHttpCode(int $http_code = null): self
162
    {
163 35
        Validator::assertIsType('http_code', $http_code, [Validator::TYPE_INTEGER,
164 35
                                                          Validator::TYPE_NULL]);
165 35
        $this->http_code = $http_code;
166
167 35
        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 31
    public function withData($data = null): self
176
    {
177 31
        Validator::assertIsType('data', $data, [Validator::TYPE_ARRAY,
178 31
                                                Validator::TYPE_OBJECT,
179 31
                                                Validator::TYPE_NULL]);
180 29
        $this->data = $data;
181
182 29
        return $this;
183
    }
184
185
    /**
186
     * @param int|null $json_opts
187
     *
188
     * @return $this
189
     */
190 19
    public function withJsonOptions(int $json_opts = null): self
191
    {
192 19
        Validator::assertIsType('json_opts', $json_opts, [Validator::TYPE_INTEGER,
193 19
                                                          Validator::TYPE_NULL]);
194 19
        $this->json_opts = $json_opts;
195
196 19
        return $this;
197
    }
198
199
    /**
200
     * @param array|null $debug_data
201
     *
202
     * @return $this
203
     */
204 10
    public function withDebugData(array $debug_data = null): self
205
    {
206 10
        Validator::assertIsType('$debug_data', $debug_data, [Validator::TYPE_ARRAY,
207 10
                                                             Validator::TYPE_NULL]);
208 10
        $this->debug_data = $debug_data;
209
210 10
        return $this;
211
    }
212
213
    /**
214
     * @param string|null $msg
215
     *
216
     * @return $this
217
     */
218 13
    public function withMessage(string $msg = null): self
219
    {
220 13
        Validator::assertIsType('message', $msg, [Validator::TYPE_STRING,
221 13
                                                  Validator::TYPE_NULL]);
222 13
        $this->message = $msg;
223
224 13
        return $this;
225
    }
226
227
    /**
228
     * @param array|null $placeholders
229
     *
230
     * @return $this
231
     */
232 19
    public function withPlaceholders(array $placeholders = null): self
233
    {
234 19
        $this->placeholders = $placeholders;
235
236 19
        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 38
    public function build(): HttpResponse
259
    {
260 38
        $api_code = $this->api_code;
261 38
        Validator::assertIsInt('api_code', $api_code);
262
263 38
        $msg_or_api_code = $this->message ?? $api_code;
264 38
        $http_headers = $this->http_headers ?? [];
265
266 38
        if ($this->success) {
267 18
            $api_code = $api_code ?? BaseApiCodes::OK();
0 ignored issues
show
Deprecated Code introduced by
The function MarcinOrlowski\ResponseBuilder\BaseApiCodes::OK() has been deprecated: Configure Exception Handler to use your own API code. This method will be removed in v8. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

267
            $api_code = $api_code ?? /** @scrutinizer ignore-deprecated */ BaseApiCodes::OK();

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.

Loading history...
268 18
            $http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_OK;
269
270 18
            Validator::assertOkHttpCode($http_code);
271
272 16
            $result = $this->make($this->success, $api_code, $msg_or_api_code, $this->data, $http_code,
273 16
                $this->placeholders, $http_headers, $this->json_opts);
274
        } else {
275 20
            $http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_ERROR;
276
277 20
            Validator::assertErrorHttpCode($http_code);
278
279 20
            $result = $this->make(false, $api_code, $msg_or_api_code, $this->data, $http_code, $this->placeholders,
280 20
                $this->http_headers, $this->json_opts, $this->debug_data);
281
        }
282
283 35
        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 42
    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 42
        $http_headers = $http_headers ?? [];
312 42
        $http_code = $http_code ?? ($success ? ResponseBuilder::DEFAULT_HTTP_CODE_OK : ResponseBuilder::DEFAULT_HTTP_CODE_ERROR);
313 42
        $json_opts = $json_opts ?? Config::get(ResponseBuilder::CONF_KEY_ENCODING_OPTIONS, ResponseBuilder::DEFAULT_ENCODING_OPTIONS);
314
315 42
        Validator::assertIsInt('encoding_options', $json_opts);
316
317 41
        Validator::assertIsInt('api_code', $api_code);
318 41
        if (!BaseApiCodes::isCodeValid($api_code)) {
319 1
            Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
320
        }
321
322 40
        return Response::json(
323 40
            $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 40
    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 40
        $data = (new Converter())->convert($data);
353 39
        if ($data !== null && !is_object($data)) {
354
            // ensure we get object in final JSON structure in data node
355 12
            $data = (object)$data;
356
        }
357
358
        // get human readable message for API code or use message string (if given instead of API code)
359 39
        if (is_int($msg_or_api_code)) {
360 24
            $message = $this->getMessageForApiCode($success, $msg_or_api_code, $placeholders);
361
        } else {
362 15
            Validator::assertIsString('message', $msg_or_api_code);
363 13
            $message = $msg_or_api_code;
364
        }
365
366
        /** @noinspection PhpUndefinedClassInspection */
367
        $response = [
368 37
            ResponseBuilder::KEY_SUCCESS => $success,
369 37
            ResponseBuilder::KEY_CODE    => $api_code,
370 37
            ResponseBuilder::KEY_LOCALE  => \App::getLocale(),
371 37
            ResponseBuilder::KEY_MESSAGE => $message,
372 37
            ResponseBuilder::KEY_DATA    => $data,
373
        ];
374
375 37
        if ($debug_data !== null) {
376 12
            $debug_key = Config::get(ResponseBuilder::CONF_KEY_DEBUG_DEBUG_KEY, ResponseBuilder::KEY_DEBUG);
377 12
            $response[ $debug_key ] = $debug_data;
378
        }
379
380 37
        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 24
    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 24
        $key = BaseApiCodes::getCodeMessageKey($api_code);
400 24
        if ($key === null) {
401
            // nope, let's get the default one instead, based of
402 2
            $fallback_code = $success ? BaseApiCodes::OK() : BaseApiCodes::NO_ERROR_MESSAGE();
0 ignored issues
show
Deprecated Code introduced by
The function MarcinOrlowski\ResponseBuilder\BaseApiCodes::OK() has been deprecated: Configure Exception Handler to use your own API code. This method will be removed in v8. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

402
            $fallback_code = $success ? /** @scrutinizer ignore-deprecated */ BaseApiCodes::OK() : BaseApiCodes::NO_ERROR_MESSAGE();

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.

Loading history...
403 2
            $key = BaseApiCodes::getCodeMessageKey($fallback_code);
404
        }
405
406 24
        $placeholders = $placeholders ?? [];
407 24
        if (!array_key_exists('api_code', $placeholders)) {
408 22
            $placeholders['api_code'] = $api_code;
409
        }
410
411 24
        return \Lang::get($key, $placeholders);
412
    }
413
}
414