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

ResponseBuilder::buildSuccessResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 1
eloc 9
c 7
b 0
f 0
nc 1
nop 6
dl 0
loc 15
ccs 10
cts 10
cp 1
crap 1
rs 9.9666

1 Method

Rating   Name   Duplication   Size   Complexity  
A ResponseBuilder::withPlaceholders() 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
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