Completed
Push — master ( 724ebd...bb6c14 )
by Marcin
18s queued 11s
created

ResponseBuilder::errorWithDataAndHttpCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 5
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 2
b 0
f 0
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