Completed
Push — master ( 724ebd...bb6c14 )
by Marcin
18s 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 3
CRAP Score 3

Importance

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

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
    protected function __construct(bool $success, int $api_code)
64
    {
65
        $this->success = $success;
66
        $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
     */
87
    public static function success($data = null, $api_code = null, array $placeholders = null,
88
                                   int $http_code = null, int $json_opts = null): HttpResponse
89
    {
90
        return ResponseBuilder::asSuccess($api_code)
91
            ->withData($data)
92
            ->withPlaceholders($placeholders)
93
            ->withHttpCode($http_code)
94
            ->withJsonOptions($json_opts)
95
            ->build();
96
    }
97
98
    /**
99 4
     * 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 4
     *
102
     * @param integer           $api_code      Your API code to be returned with the response object.
103 4
     * @param array|null        $placeholders  Placeholders passed to Lang::get() for message placeholders
104 3
     *                                         substitution or @null if none.
105 2
     * @param object|array|null $data          Array of primitives and supported objects to be returned in 'data' node
106 2
     *                                         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 1
     * @param integer|null      $json_opts     See http://php.net/manual/en/function.json-encode.php for supported
111 1
     *                                         options or pass @null to use value from your config (or defaults).
112
     *
113 1
     * @return HttpResponse
114 1
     */
115 1
    public static function error(int $api_code, array $placeholders = null, $data = null, int $http_code = null,
116 1
                                 int $json_opts = null): HttpResponse
117
    {
118
        return ResponseBuilder::asError($api_code)
119
            ->withPlaceholders($placeholders)
120
            ->withData($data)
121 1
            ->withHttpCode($http_code)
122
            ->withJsonOptions($json_opts)
123
            ->build();
124 1
    }
125
126
// -----------------------------------------------------------------------------------------------------------
127
128
    public static function asSuccess(int $api_code = null): self
129
    {
130
        return new self(true, $api_code ?? BaseApiCodes::OK());
131
    }
132
133
    public static function asError(int $api_code): self
134
    {
135
        $code_ok = BaseApiCodes::OK();
136
        if ($api_code !== $code_ok) {
137
            Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
138
        }
139 12
        if ($api_code === $code_ok) {
140
            throw new \InvalidArgumentException(
141
                "Error response cannot use api_code of value {$code_ok} which is reserved for OK");
142 12
        }
143
144
        return new self(false, $api_code);
145
    }
146
147
    public function withHttpCode(int $http_code = null): self
148
    {
149
        Validator::assertIsType('http_code', $http_code, [Validator::TYPE_INTEGER,
150
                                                          Validator::TYPE_NULL]);
151
        $this->http_code = $http_code;
152
153
        return $this;
154 1
    }
155
156 1
    public function withData($data = null): self
157
    {
158
        Validator::assertIsType('data', $data, [Validator::TYPE_ARRAY,
159
                                                Validator::TYPE_OBJECT,
160
                                                Validator::TYPE_NULL]);
161
        $this->data = $data;
162
163
        return $this;
164
    }
165
166
    public function withJsonOptions(int $json_opts = null): self
167 4
    {
168
        Validator::assertIsType('json_opts', $json_opts, [Validator::TYPE_INTEGER,
169 4
                                                          Validator::TYPE_NULL]);
170
        $this->json_opts = $json_opts;
171
172
        return $this;
173
    }
174
175
    public function withDebugData(array $debug_data = null): self
176
    {
177
        Validator::assertIsType('$debug_data', $debug_data, [Validator::TYPE_ARRAY,
178
                                                             Validator::TYPE_NULL]);
179
        $this->debug_data = $debug_data;
180
181
        return $this;
182
    }
183
184 16
    public function withMessage(string $msg = null): self
185
    {
186
        Validator::assertIsType('message', $msg, [Validator::TYPE_STRING,
187 16
                                                  Validator::TYPE_NULL]);
188 16
        $this->message = $msg;
189
190 16
        return $this;
191 16
    }
192 16
193
    public function withPlaceholders(array $placeholders = null): self
194 14
    {
195
        $this->placeholders = $placeholders;
196
197
        return $this;
198
    }
199
200
    public function withHttpHeaders(array $http_headers = null): self
201
    {
202
        $this->http_headers = $http_headers ?? [];
203
204
        return $this;
205
    }
206
207
    public function build(): HttpResponse
208
    {
209
        $api_code = $this->api_code;
210 4
        Validator::assertIsInt('api_code', $api_code);
211
212
        $msg_or_api_code = $this->message ?? $api_code;
213 4
        $http_headers = $this->http_headers ?? [];
214
215
        if ($this->success) {
216
            $api_code = $api_code ?? BaseApiCodes::OK();
217
            $http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_OK;
218
219
            Validator::assertOkHttpCode($http_code);
220
221
            $result = $this->make($this->success, $api_code, $msg_or_api_code, $this->data, $http_code,
222
                $this->placeholders, $http_headers, $this->json_opts);
223
        } else {
224
            $http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_ERROR;
225 1
226
            Validator::assertErrorHttpCode($http_code);
227
228 1
            $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
                $this->placeholders, $this->http_headers, $this->json_opts, $this->debug_data);
230
231
        }
232
233
        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 1
     *                                           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 1
     * @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 1
    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 1
                            int $json_opts = null, array $debug_data = null): HttpResponse
261
    {
262
        $http_headers = $http_headers ?? [];
263
        $http_code = $http_code ?? ($success ? ResponseBuilder::DEFAULT_HTTP_CODE_OK : ResponseBuilder::DEFAULT_HTTP_CODE_ERROR);
264
        $json_opts = $json_opts ?? Config::get(ResponseBuilder::CONF_KEY_ENCODING_OPTIONS, ResponseBuilder::DEFAULT_ENCODING_OPTIONS);
265
266
        Validator::assertIsInt('encoding_options', $json_opts);
267
268
        Validator::assertIsInt('api_code', $api_code);
269
        if (!BaseApiCodes::isCodeValid($api_code)) {
270
            Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
271
        }
272
273 1
        return Response::json(
274
            $this->buildResponse($success, $api_code, $msg_or_api_code, $placeholders, $data, $debug_data),
275
            $http_code, $http_headers, $json_opts);
276 1
    }
277 1
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 6
     *
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 6
     */
296 6
    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
        $data = (new Converter())->convert($data);
302
        if ($data !== null && !is_object($data)) {
303
            // ensure we get object in final JSON structure in data node
304
            $data = (object)$data;
305
        }
306 1
307
        // get human readable message for API code or use message string (if given instead of API code)
308 1
        if (is_int($msg_or_api_code)) {
309
            $message = $this->getMessageForApiCode($success, $msg_or_api_code, $placeholders);
310
        } else {
311
            Validator::assertIsString('message', $msg_or_api_code);
312
            $message = $msg_or_api_code;
313
        }
314
315
        /** @noinspection PhpUndefinedClassInspection */
316
        $response = [
317
            ResponseBuilder::KEY_SUCCESS => $success,
318
            ResponseBuilder::KEY_CODE    => $api_code,
319
            ResponseBuilder::KEY_LOCALE  => \App::getLocale(),
320
            ResponseBuilder::KEY_MESSAGE => $message,
321
            ResponseBuilder::KEY_DATA    => $data,
322
        ];
323
324
        if ($debug_data !== null) {
325
            $debug_key = Config::get(ResponseBuilder::CONF_KEY_DEBUG_DEBUG_KEY, ResponseBuilder::KEY_DEBUG);
326
            $response[ $debug_key ] = $debug_data;
327
        }
328
329
        return $response;
330
    }
331 18
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 18
     * returns it unaltered.
336 18
     *
337
     * @param boolean    $success      @true if response reports successful operation, @false otherwise.
338 18
     * @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 18
     *                                 substitution or @null if none.
341 18
     *
342 16
     * @return string
343
     */
344 18
    protected function getMessageForApiCode(bool $success, int $api_code, array $placeholders = null): string
345 2
    {
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 16
        $key = BaseApiCodes::getCodeMessageKey($api_code);
349 16
        if ($key === null) {
350
            // nope, let's get the default one instead, based of
351 15
            $fallback_code = $success ? BaseApiCodes::OK() : BaseApiCodes::NO_ERROR_MESSAGE();
352
            $key = BaseApiCodes::getCodeMessageKey($fallback_code);
353 15
        }
354 15
355
        $placeholders = $placeholders ?? [];
356
        if (!array_key_exists('api_code', $placeholders)) {
357
            $placeholders['api_code'] = $api_code;
358
        }
359
360
        return \Lang::get($key, $placeholders);
361
    }
362
}
363