Completed
Push — master ( bb6c14...7b9d4f )
by Marcin
23s queued 11s
created

ResponseBuilder   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 387
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 29
Bugs 0 Features 0
Metric Value
eloc 109
c 29
b 0
f 0
dl 0
loc 387
ccs 77
cts 77
cp 1
rs 10
wmc 28

16 Methods

Rating   Name   Duplication   Size   Complexity  
A buildResponse() 0 34 5
A withMessage() 0 7 1
A withDebugData() 0 7 1
A success() 0 9 1
A asError() 0 12 3
A getMessageForApiCode() 0 17 4
A withJsonOptions() 0 7 1
A make() 0 18 3
A build() 0 26 2
A asSuccess() 0 3 1
A __construct() 0 4 1
A error() 0 9 1
A withHttpCode() 0 7 1
A withPlaceholders() 0 5 1
A withData() 0 8 1
A withHttpHeaders() 0 5 1
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
    protected function __construct(bool $success, int $api_code)
63
    {
64
        $this->success = $success;
65
        $this->api_code = $api_code;
66
    }
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
    public static function success($data = null, $api_code = null, array $placeholders = null,
87
                                   int $http_code = null, int $json_opts = null): HttpResponse
88
    {
89
        return ResponseBuilder::asSuccess($api_code)
90
            ->withData($data)
91
            ->withPlaceholders($placeholders)
92
            ->withHttpCode($http_code)
93
            ->withJsonOptions($json_opts)
94
            ->build();
95
    }
96
97
    /**
98
     * Builds error Response object. Supports optional arguments passed to Lang::get() if associated error
99 4
     * message uses placeholders as well as return data payload
100
     *
101 4
     * @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 4
     *                                         substitution or @null if none.
104 3
     * @param object|array|null $data          Array of primitives and supported objects to be returned in 'data' node
105 2
     *                                         of the JSON response, single supported object or @null if there's no
106 2
     *                                         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 1
     *                                         options or pass @null to use value from your config (or defaults).
111 1
     *
112
     * @return HttpResponse
113 1
     */
114 1
    public static function error(int $api_code, array $placeholders = null, $data = null, int $http_code = null,
115 1
                                 int $json_opts = null): HttpResponse
116 1
    {
117
        return ResponseBuilder::asError($api_code)
118
            ->withPlaceholders($placeholders)
119
            ->withData($data)
120
            ->withHttpCode($http_code)
121 1
            ->withJsonOptions($json_opts)
122
            ->build();
123
    }
124 1
125
    // -----------------------------------------------------------------------------------------------------------
126
127
    /**
128
     * @param int|null $api_code
129
     *
130
     * @return \MarcinOrlowski\ResponseBuilder\ResponseBuilder
131
     */
132
    public static function asSuccess(int $api_code = null): self
133
    {
134
        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 12
     *
140
     * @return \MarcinOrlowski\ResponseBuilder\ResponseBuilder
141
     */
142 12
    public static function asError(int $api_code): self
143
    {
144
        $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
        if ($api_code !== $code_ok) {
146
            Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
147
        }
148
        if ($api_code === $code_ok) {
149
            throw new \InvalidArgumentException(
150
                "Error response cannot use api_code of value {$code_ok} which is reserved for OK");
151
        }
152
153
        return new self(false, $api_code);
154 1
    }
155
156 1
    /**
157
     * @param int|null $http_code
158
     *
159
     * @return $this
160
     */
161
    public function withHttpCode(int $http_code = null): self
162
    {
163
        Validator::assertIsType('http_code', $http_code, [Validator::TYPE_INTEGER,
164
                                                          Validator::TYPE_NULL]);
165
        $this->http_code = $http_code;
166
167 4
        return $this;
168
    }
169 4
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
    public function withData($data = null): self
176
    {
177
        Validator::assertIsType('data', $data, [Validator::TYPE_ARRAY,
178
                                                Validator::TYPE_OBJECT,
179
                                                Validator::TYPE_NULL]);
180
        $this->data = $data;
181
182
        return $this;
183
    }
184 16
185
    /**
186
     * @param int|null $json_opts
187 16
     *
188 16
     * @return $this
189
     */
190 16
    public function withJsonOptions(int $json_opts = null): self
191 16
    {
192 16
        Validator::assertIsType('json_opts', $json_opts, [Validator::TYPE_INTEGER,
193
                                                          Validator::TYPE_NULL]);
194 14
        $this->json_opts = $json_opts;
195
196
        return $this;
197
    }
198
199
    /**
200
     * @param array|null $debug_data
201
     *
202
     * @return $this
203
     */
204
    public function withDebugData(array $debug_data = null): self
205
    {
206
        Validator::assertIsType('$debug_data', $debug_data, [Validator::TYPE_ARRAY,
207
                                                             Validator::TYPE_NULL]);
208
        $this->debug_data = $debug_data;
209
210 4
        return $this;
211
    }
212
213 4
    /**
214
     * @param string|null $msg
215
     *
216
     * @return $this
217
     */
218
    public function withMessage(string $msg = null): self
219
    {
220
        Validator::assertIsType('message', $msg, [Validator::TYPE_STRING,
221
                                                  Validator::TYPE_NULL]);
222
        $this->message = $msg;
223
224
        return $this;
225 1
    }
226
227
    /**
228 1
     * @param array|null $placeholders
229
     *
230
     * @return $this
231
     */
232
    public function withPlaceholders(array $placeholders = null): self
233
    {
234
        $this->placeholders = $placeholders;
235
236
        return $this;
237
    }
238
239
    /**
240
     * @param array|null $http_headers
241
     *
242
     * @return $this
243 1
     */
244
    public function withHttpHeaders(array $http_headers = null): self
245
    {
246 1
        $this->http_headers = $http_headers ?? [];
247
248
        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 1
    public function build(): HttpResponse
259
    {
260 1
        $api_code = $this->api_code;
261
        Validator::assertIsInt('api_code', $api_code);
262
263
        $msg_or_api_code = $this->message ?? $api_code;
264
        $http_headers = $this->http_headers ?? [];
265
266
        if ($this->success) {
267
            $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
            $http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_OK;
269
270
            Validator::assertOkHttpCode($http_code);
271
272
            $result = $this->make($this->success, $api_code, $msg_or_api_code, $this->data, $http_code,
273 1
                $this->placeholders, $http_headers, $this->json_opts);
274
        } else {
275
            $http_code = $this->http_code ?? ResponseBuilder::DEFAULT_HTTP_CODE_ERROR;
276 1
277 1
            Validator::assertErrorHttpCode($http_code);
278
279
            $result = $this->make(false, $api_code, $msg_or_api_code, $this->data, $http_code, $this->placeholders,
280
                $this->http_headers, $this->json_opts, $this->debug_data);
281
        }
282
283
        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 6
     * @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 6
     * @param array|null        $http_headers    Optional HTTP headers to be returned in the response.
296 6
     * @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 1
     */
307
    protected function make(bool $success, int $api_code, $msg_or_api_code, $data = null,
308 1
                            int $http_code = null, array $placeholders = null, array $http_headers = null,
309
                            int $json_opts = null, array $debug_data = null): HttpResponse
310
    {
311
        $http_headers = $http_headers ?? [];
312
        $http_code = $http_code ?? ($success ? ResponseBuilder::DEFAULT_HTTP_CODE_OK : ResponseBuilder::DEFAULT_HTTP_CODE_ERROR);
313
        $json_opts = $json_opts ?? Config::get(ResponseBuilder::CONF_KEY_ENCODING_OPTIONS, ResponseBuilder::DEFAULT_ENCODING_OPTIONS);
314
315
        Validator::assertIsInt('encoding_options', $json_opts);
316
317
        Validator::assertIsInt('api_code', $api_code);
318
        if (!BaseApiCodes::isCodeValid($api_code)) {
319
            Validator::assertIsIntRange('api_code', $api_code, BaseApiCodes::getMinCode(), BaseApiCodes::getMaxCode());
320
        }
321
322
        return Response::json(
323
            $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 18
     * 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 18
     * @param string|integer    $msg_or_api_code Message string or valid API code to get message for.
336 18
     * @param array|null        $placeholders    Placeholders passed to Lang::get() for message placeholders
337
     *                                           substitution or @null if none.
338 18
     * @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 18
     *
341 18
     * @return array response ready to be encoded as json and sent back to client
342 16
     *
343
     * @throws \RuntimeException in case of missing or invalid "classes" mapping configuration
344 18
     *
345 2
     * @noinspection PhpTooManyParametersInspection
346
     */
347
    protected function buildResponse(bool $success, int $api_code,
348 16
                                     $msg_or_api_code, array $placeholders = null,
349 16
                                     $data = null, array $debug_data = null): array
350
    {
351 15
        // ensure $data is either @null, array or object of class with configured mapping.
352
        $data = (new Converter())->convert($data);
353 15
        if ($data !== null && !is_object($data)) {
354 15
            // ensure we get object in final JSON structure in data node
355
            $data = (object)$data;
356
        }
357
358
        // get human readable message for API code or use message string (if given instead of API code)
359
        if (is_int($msg_or_api_code)) {
360
            $message = $this->getMessageForApiCode($success, $msg_or_api_code, $placeholders);
361
        } else {
362
            Validator::assertIsString('message', $msg_or_api_code);
363
            $message = $msg_or_api_code;
364
        }
365
366
        /** @noinspection PhpUndefinedClassInspection */
367
        $response = [
368
            ResponseBuilder::KEY_SUCCESS => $success,
369
            ResponseBuilder::KEY_CODE    => $api_code,
370
            ResponseBuilder::KEY_LOCALE  => \App::getLocale(),
371
            ResponseBuilder::KEY_MESSAGE => $message,
372
            ResponseBuilder::KEY_DATA    => $data,
373
        ];
374
375 35
        if ($debug_data !== null) {
376
            $debug_key = Config::get(ResponseBuilder::CONF_KEY_DEBUG_DEBUG_KEY, ResponseBuilder::KEY_DEBUG);
377
            $response[ $debug_key ] = $debug_data;
378
        }
379 35
380 35
        return $response;
381 35
    }
382
383 35
    /**
384
     * If $msg_or_api_code is integer value, returns human readable message associated with that code (with
385 34
     * fallback to built-in default string if no api code mapping is set. If $msg_or_api_code is a string,
386 34
     * returns it unaltered.
387 1
     *
388 1
     * @param boolean    $success      @true if response reports successful operation, @false otherwise.
389 1
     * @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 33
     *
393 2
     * @return string
394 2
     */
395 2
    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
        $key = BaseApiCodes::getCodeMessageKey($api_code);
400
        if ($key === null) {
401 31
            // nope, let's get the default one instead, based of
402 23
            $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 23
            $key = BaseApiCodes::getCodeMessageKey($fallback_code);
404
        }
405 2
406
        $placeholders = $placeholders ?? [];
407
        if (!array_key_exists('api_code', $placeholders)) {
408 23
            $placeholders['api_code'] = $api_code;
409 23
        }
410
411
        return \Lang::get($key, $placeholders);
412 31
    }
413
}
414