Passed
Pull Request — dev (#124)
by Marcin
02:46
created

ResponseBuilder::buildErrorResponse()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3

Importance

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

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
28
    /** @var bool */
29
    protected $success = false;
30
31
    /** @var int|null */
32
    protected $api_code = null;
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 45
    protected function __construct(bool $success, int $api_code)
64
    {
65 45
        $this->success = $success;
66 45
        $this->api_code = $api_code;
67 45
    }
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 6
    public static function success($data = null, $api_code = null, array $placeholders = null,
88
                                   int $http_code = null, int $json_opts = null): HttpResponse
89
    {
90 6
        return ResponseBuilder::asSuccess($api_code)
91 6
            ->withData($data)
92 4
            ->withPlaceholders($placeholders)
93 4
            ->withHttpCode($http_code)
94 4
            ->withJsonOptions($json_opts)
95 4
            ->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 ResponseBuilder::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 26
    public static function asSuccess(int $api_code = null): self
129
    {
130 26
        return new self(true, $api_code ?? BaseApiCodes::OK());
131
    }
132
133 21
    public static function asError(int $api_code): self
134
    {
135 21
        $code_ok = BaseApiCodes::OK();
136 21
        if ($api_code !== $code_ok) {
137 19
            Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
138
        }
139 21
        if ($api_code === $code_ok) {
140 2
            throw new \InvalidArgumentException(
141 2
                "Error response cannot use api_code of value {$code_ok} which is reserved for OK");
142
        }
143
144 19
        return new self(false, $api_code);
145
    }
146
147 34
    public function withHttpCode(int $http_code = null): self
148
    {
149 34
        Validator::assertIsType('http_code', $http_code, [Validator::TYPE_INTEGER,
150 34
                                                          Validator::TYPE_NULL]);
151 34
        $this->http_code = $http_code;
152
153 34
        return $this;
154
    }
155
156 30
    public function withData($data = null): self
157
    {
158 30
        Validator::assertIsType('data', $data, [Validator::TYPE_ARRAY,
159 30
                                                Validator::TYPE_OBJECT,
160 30
                                                Validator::TYPE_NULL]);
161 28
        $this->data = $data;
162
163 28
        return $this;
164
    }
165
166 19
    public function withJsonOptions(int $json_opts = null): self
167
    {
168 19
        Validator::assertIsType('json_opts', $json_opts, [Validator::TYPE_INTEGER,
169 19
                                                          Validator::TYPE_NULL]);
170 19
        $this->json_opts = $json_opts;
171
172 19
        return $this;
173
    }
174
175 9
    public function withDebugData(array $debug_data = null): self
176
    {
177 9
        Validator::assertIsType('$debug_data', $debug_data, [Validator::TYPE_ARRAY,
178 9
                                                             Validator::TYPE_NULL]);
179 9
        $this->debug_data = $debug_data;
180
181 9
        return $this;
182
    }
183
184 12
    public function withMessage(string $msg = null): self
185
    {
186 12
        Validator::assertIsType('message', $msg, [Validator::TYPE_STRING,
187 12
                                                  Validator::TYPE_NULL]);
188 12
        $this->message = $msg;
189
190 12
        return $this;
191
    }
192
193 19
    public function withPlaceholders(array $placeholders = null): self
194
    {
195 19
        $this->placeholders = $placeholders;
196
197 19
        return $this;
198
    }
199
200 1
    public function withHttpHeaders(array $http_headers = null): self
201
    {
202 1
        $this->http_headers = $http_headers ?? [];
203
204 1
        return $this;
205
    }
206
207 37
    public function build(): HttpResponse
208
    {
209 37
        $api_code = $this->api_code;
210 37
        Validator::assertIsInt('api_code', $api_code);
211
212 37
        $msg_or_api_code = $this->message ?? $api_code;
213 37
        $http_headers = $this->http_headers ?? [];
214
215 37
        if ($this->success) {
216 18
            $api_code = $api_code ?? BaseApiCodes::OK();
217 18
            $http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_OK;
218
219 18
            Validator::assertOkHttpCode($http_code);
220
221 16
            $result = $this->make($this->success, $api_code, $msg_or_api_code, $this->data, $http_code,
222 16
                $this->placeholders, $http_headers, $this->json_opts);
223
        } else {
224 19
            $http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_ERROR;
225
226 19
            Validator::assertErrorHttpCode($http_code);
227
228 19
            $result = $this->make(false, $api_code, $msg_or_api_code, $this->data, $http_code,
0 ignored issues
show
Bug introduced by
It seems like $api_code can also be of type null; however, parameter $api_code of MarcinOrlowski\ResponseB...ResponseBuilder::make() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

228
            $result = $this->make(false, /** @scrutinizer ignore-type */ $api_code, $msg_or_api_code, $this->data, $http_code,
Loading history...
229 19
                $this->placeholders, $this->http_headers, $this->json_opts, $this->debug_data);
230
231
        }
232
233 34
        return $result;
234
    }
235
236
237
    /**
238
     * @param boolean           $success         @true if response reports successful operation, @false otherwise.
239
     * @param integer           $api_code        Your API code to be returned with the response object.
240
     * @param string|integer    $msg_or_api_code message string or valid API code to get message for
241
     * @param object|array|null $data            optional additional data to be included in response object
242
     * @param integer|null      $http_code       HTTP code for the HttpResponse or @null for either DEFAULT_HTTP_CODE_OK
243
     *                                           or DEFAULT_HTTP_CODE_ERROR depending on the $success.
244
     * @param array|null        $placeholders    Placeholders passed to Lang::get() for message placeholders
245
     *                                           substitution or @null if none.
246
     * @param array|null        $http_headers    Optional HTTP headers to be returned in the response.
247
     * @param integer|null      $json_opts       See http://php.net/manual/en/function.json-encode.php for supported
248
     *                                           options or pass @null to use value from your config (or defaults).
249
     * @param array|null        $debug_data      Optional debug data array to be added to returned JSON.
250
     *
251
     * @return HttpResponse
252
     *
253
     * @throws \InvalidArgumentException If $api_code is neither a string nor valid integer code.
254
     * @throws \InvalidArgumentException if $data is an object of class that is not configured in "classes" mapping.
255
     *
256
     * @noinspection MoreThanThreeArgumentsInspection
257
     */
258 41
    protected function make(bool $success, int $api_code, $msg_or_api_code, $data = null,
259
                            int $http_code = null, array $placeholders = null, array $http_headers = null,
260
                            int $json_opts = null, array $debug_data = null): HttpResponse
261
    {
262 41
        $http_headers = $http_headers ?? [];
263 41
        $http_code = $http_code ?? ($success ? ResponseBuilder::DEFAULT_HTTP_CODE_OK : ResponseBuilder::DEFAULT_HTTP_CODE_ERROR);
264 41
        $json_opts = $json_opts ?? Config::get(ResponseBuilder::CONF_KEY_ENCODING_OPTIONS, ResponseBuilder::DEFAULT_ENCODING_OPTIONS);
265
266 41
        Validator::assertIsInt('encoding_options', $json_opts);
267
268 40
        Validator::assertIsInt('api_code', $api_code);
269 40
        if (!BaseApiCodes::isCodeValid($api_code)) {
270 1
            Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
271
        }
272
273 39
        return Response::json(
274 39
            $this->buildResponse($success, $api_code, $msg_or_api_code, $placeholders, $data, $debug_data),
275 36
            $http_code, $http_headers, $json_opts);
276
    }
277
278
    /**
279
     * Creates standardised API response array. This is final method called in the whole pipeline before we
280
     * return final JSON back to client. If you want to manipulate your response, this is the place to do that.
281
     * If you set APP_DEBUG to true, 'code_hex' field will be additionally added to reported JSON for easier
282
     * manual debugging.
283
     *
284
     * @param boolean           $success         @true if response reports successful operation, @false otherwise.
285
     * @param integer           $api_code        Your API code to be returned with the response object.
286
     * @param string|integer    $msg_or_api_code Message string or valid API code to get message for.
287
     * @param array|null        $placeholders    Placeholders passed to Lang::get() for message placeholders
288
     *                                           substitution or @null if none.
289
     * @param object|array|null $data            API response data if any
290
     * @param array|null        $debug_data      optional debug data array to be added to returned JSON.
291
     *
292
     * @return array response ready to be encoded as json and sent back to client
293
     *
294
     * @throws \RuntimeException in case of missing or invalid "classes" mapping configuration
295
     */
296 39
    protected function buildResponse(bool $success, int $api_code,
297
                                     $msg_or_api_code, array $placeholders = null,
298
                                     $data = null, array $debug_data = null): array
299
    {
300
        // ensure $data is either @null, array or object of class with configured mapping.
301 39
        $data = (new Converter())->convert($data);
302 38
        if ($data !== null && !is_object($data)) {
303
            // ensure we get object in final JSON structure in data node
304 12
            $data = (object)$data;
305
        }
306
307
        // get human readable message for API code or use message string (if given instead of API code)
308 38
        if (is_int($msg_or_api_code)) {
309 24
            $message = $this->getMessageForApiCode($success, $msg_or_api_code, $placeholders);
310
        } else {
311 14
            Validator::assertIsString('message', $msg_or_api_code);
312 12
            $message = $msg_or_api_code;
313
        }
314
315
        /** @noinspection PhpUndefinedClassInspection */
316
        $response = [
317 36
            ResponseBuilder::KEY_SUCCESS => $success,
318 36
            ResponseBuilder::KEY_CODE    => $api_code,
319 36
            ResponseBuilder::KEY_LOCALE  => \App::getLocale(),
320 36
            ResponseBuilder::KEY_MESSAGE => $message,
321 36
            ResponseBuilder::KEY_DATA    => $data,
322
        ];
323
324 36
        if ($debug_data !== null) {
325 12
            $debug_key = Config::get(ResponseBuilder::CONF_KEY_DEBUG_DEBUG_KEY, ResponseBuilder::KEY_DEBUG);
326 12
            $response[ $debug_key ] = $debug_data;
327
        }
328
329 36
        return $response;
330
    }
331
332
    /**
333
     * If $msg_or_api_code is integer value, returns human readable message associated with that code (with
334
     * fallback to built-in default string if no api code mapping is set. If $msg_or_api_code is a string,
335
     * returns it unaltered.
336
     *
337
     * @param boolean    $success      @true if response reports successful operation, @false otherwise.
338
     * @param integer    $api_code     Your API code to be returned with the response object.
339
     * @param array|null $placeholders Placeholders passed to Lang::get() for message placeholders
340
     *                                 substitution or @null if none.
341
     *
342
     * @return string
343
     */
344 24
    protected function getMessageForApiCode(bool $success, int $api_code, array $placeholders = null): string
345
    {
346
        // We got integer value here not a message string, so we need to check if we have the mapping for
347
        // this string already configured.
348 24
        $key = BaseApiCodes::getCodeMessageKey($api_code);
349 24
        if ($key === null) {
350
            // nope, let's get the default one instead, based of
351 2
            $fallback_code = $success ? BaseApiCodes::OK() : BaseApiCodes::NO_ERROR_MESSAGE();
352 2
            $key = BaseApiCodes::getCodeMessageKey($fallback_code);
353
        }
354
355 24
        $placeholders = $placeholders ?? [];
356 24
        if (!array_key_exists('api_code', $placeholders)) {
357 22
            $placeholders['api_code'] = $api_code;
358
        }
359
360 24
        return \Lang::get($key, $placeholders);
361
    }
362
}
363