GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( a37500...a1e398 )
by ignace
02:28
created

JSend   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 495
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 56
c 6
b 0
f 0
lcom 2
cbo 0
dl 0
loc 495
ccs 127
cts 127
cp 1
rs 6.5957

30 Methods

Rating   Name   Duplication   Size   Complexity  
A filterStatus() 0 9 2
A __construct() 0 6 1
A filterData() 0 16 4
A filterError() 0 8 2
B validateErrorMessage() 0 11 5
A validateErrorCode() 0 12 3
A getStatus() 0 4 1
A getData() 0 4 1
A getErrorMessage() 0 4 1
A getErrorCode() 0 4 1
A isSuccess() 0 4 1
A isFail() 0 4 1
A isError() 0 4 1
A __toString() 0 4 1
A jsonSerialize() 0 4 1
B toArray() 0 18 5
A __debugInfo() 0 4 1
A send() 0 12 2
A filterHeaders() 0 9 2
A validateHeaderName() 0 8 2
A validateHeaderValue() 0 10 3
A withStatus() 0 8 2
A withData() 0 9 2
A withError() 0 8 4
A success() 0 4 1
A fail() 0 4 1
A error() 0 4 1
A createFromString() 0 12 2
A __set_state() 0 4 1
A createFromArray() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like JSend often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JSend, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Carpediem\JSend;
4
5
use InvalidArgumentException;
6
use JsonSerializable;
7
use UnexpectedValueException;
8
9
/**
10
 * A Immutable Value Object Class to represent a JSend object
11
 */
12
class JSend implements JsonSerializable
13
{
14
    const STATUS_SUCCESS = 'success';
15
16
    const STATUS_ERROR = 'error';
17
18
    const STATUS_FAIL = 'fail';
19
20
    /**
21
     * JSend status
22
     *
23
     * @var string
24
     */
25
    protected $status;
26
27
    /**
28
     * JSend Data
29
     *
30
     * @var array
31
     */
32
    protected $data;
33
34
    /**
35
     * JSend Error Message
36
     *
37
     * @var string
38
     */
39
    protected $errorMessage;
40
41
    /**
42
     * JSend Error Code
43
     * @var int|null
44
     */
45
    protected $errorCode;
46
47
    /**
48
     * New Instance
49
     *
50
     * @param string $status
51
     * @param mixed  $data
52
     * @param string $errorMessage
53
     * @param int    $errorCode
54
     */
55 96
    public function __construct($status, $data = null, $errorMessage = null, $errorCode = null)
56
    {
57 96
        $this->status = $this->filterStatus($status);
58 96
        $this->data = $this->filterData($data);
59 96
        list($this->errorMessage, $this->errorCode) = $this->filterError($errorMessage, $errorCode);
60 96
    }
61
62
    /**
63
     * Filter and Validate the JSend Status
64
     *
65
     * @param string $status
66
     *
67
     * @throws UnexpectedValueException If the status value does not conform to JSend Spec.
68
     *
69
     * @return string
70
     */
71 96
    protected function filterStatus($status)
72
    {
73 96
        $res = [self::STATUS_SUCCESS => 1, self::STATUS_ERROR => 1, self::STATUS_FAIL => 1];
74 96
        if (isset($res[$status])) {
75 96
            return $status;
76
        }
77
78 6
        throw new UnexpectedValueException('The given status does not conform to Jsend specification');
79
    }
80
81
    /**
82
     * Filter and Validate the JSend Data
83
     *
84
     * @param mixed $data The data can be
85
     *                    <ul>
86
     *                    <li>An Array
87
     *                    <li>A JsonSerializable object
88
     *                    <li>null
89
     *                    </ul>
90
     *
91
     * @throws UnexpectedValueException If the input does not conform to one of the valid type
92
     *
93
     * @return array
94
     */
95 96
    protected function filterData($data)
96
    {
97 96
        if (null === $data) {
98 9
            return [];
99
        }
100
101 96
        if ($data instanceof JsonSerializable) {
102 3
            return $data->jsonSerialize();
103
        }
104
105 96
        if (is_array($data)) {
106 96
            return $data;
107
        }
108
109 3
        throw new UnexpectedValueException('The data must be an array, a JsonSerializable object or null');
110
    }
111
112
    /**
113
     * Filter and Validate the JSend Error properties
114
     *
115
     * @param string $errorMessage
116
     * @param int    $errorCode
117
     */
118 96
    protected function filterError($errorMessage, $errorCode)
119
    {
120 96
        if (self::STATUS_ERROR !== $this->status) {
121 96
            return;
122
        }
123
124 30
        return [$this->validateErrorMessage($errorMessage), $this->validateErrorCode($errorCode)];
125
    }
126
127
    /**
128
     * Validate a string
129
     *
130
     * @param mixed $str
131
     *
132
     * @throws UnexpectedValueException If the data value is not a empty string
133
     *
134
     * @return string
135
     */
136 30
    protected function validateErrorMessage($str)
137
    {
138 30
        if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) {
139 27
            $str = (string) $str;
140 27
            if ('' !== $str) {
141 24
                return $str;
142
            }
143 2
        }
144
145 6
        throw new UnexpectedValueException('The error message must be a non empty string');
146
    }
147
148
    /**
149
     * Validate a integer
150
     *
151
     * @param mixed $int
152
     *
153
     * @throws UnexpectedValueException If the data value is not an integer
154
     *
155
     * @return int
156
     */
157 24
    protected function validateErrorCode($int)
158
    {
159 24
        if (null === $int) {
160 9
            return $int;
161
        }
162
163 18
        if (false !== ($res = filter_var($int, FILTER_VALIDATE_INT))) {
164 15
            return $res;
165
        }
166
167 3
        throw new UnexpectedValueException('The error code must be a integer or null');
168
    }
169
170
    /**
171
     * Returns the status
172
     *
173
     * @return string
174
     */
175 39
    public function getStatus()
176
    {
177 39
        return $this->status;
178
    }
179
180
    /**
181
     * Returns the data
182
     *
183
     * @return array
184
     */
185 33
    public function getData()
186
    {
187 33
        return $this->data;
188
    }
189
190
    /**
191
     * Returns the error message
192
     *
193
     * @return string
194
     */
195 12
    public function getErrorMessage()
196
    {
197 12
        return $this->errorMessage;
198
    }
199
200
    /**
201
     * Returns the error code
202
     *
203
     * @return int|null
204
     */
205 15
    public function getErrorCode()
206
    {
207 15
        return $this->errorCode;
208
    }
209
210
    /**
211
     * Returns whether the status is success
212
     *
213
     * @return bool
214
     */
215 9
    public function isSuccess()
216
    {
217 9
        return self::STATUS_SUCCESS === $this->status;
218
    }
219
220
    /**
221
     * Returns whether the status is fail
222
     *
223
     * @return bool
224
     */
225 9
    public function isFail()
226
    {
227 9
        return self::STATUS_FAIL === $this->status;
228
    }
229
230
    /**
231
     * Returns whether the status is error
232
     *
233
     * @return bool
234
     */
235 15
    public function isError()
236
    {
237 15
        return self::STATUS_ERROR === $this->status;
238
    }
239
240
    /**
241
     * @inheritdoc
242
     */
243 45
    public function __toString()
244
    {
245 45
        return json_encode($this, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
246
    }
247
248
    /**
249
     * @inheritdoc
250
     */
251 45
    public function jsonSerialize()
252
    {
253 45
        return $this->toArray();
254
    }
255
256
    /**
257
     * Retuns the array representation
258
     *
259
     * @return array
260
     */
261 48
    public function toArray()
262
    {
263 48
        $arr = ['status' => $this->status, 'data' => $this->data ?: null];
264 48
        if (self::STATUS_ERROR !== $this->status) {
265 33
            return $arr;
266
        }
267
268 15
        $arr['message'] = (string) $this->errorMessage;
269 15
        if (null !== $this->errorCode) {
270 9
            $arr['code'] = $this->errorCode;
271 6
        }
272
273 15
        if (null === $arr['data']) {
274 9
            unset($arr['data']);
275 6
        }
276
277 15
        return $arr;
278
    }
279
280
    /**
281
     * @inheritdoc
282
     */
283 3
    public function __debugInfo()
284
    {
285 3
        return $this->toArray();
286
    }
287
288
    /**
289
     * Returns the generated HTTP Response
290
     *
291
     * @param array $headers Optional headers to add to the response
292
     *
293
     * @return string
294
     */
295 12
    public function send(array $headers = [])
296
    {
297 12
        $body = $this->__toString();
298 12
        $headers = $this->filterHeaders(array_merge([
299 12
            'Content-Type' => 'application/json;charset=utf-8',
300 12
            'Content-Length' => strlen($body),
301 8
        ], $headers));
302 3
        foreach ($headers as $header) {
303 3
            header($header);
304 2
        }
305 3
        echo $body;
306 3
    }
307
308
    /**
309
     * Filter Submitted Headers
310
     *
311
     * @param array $headers a Collection of key/value headers
312
     *
313
     * @return array
314
     */
315 12
    protected function filterHeaders(array $headers)
316
    {
317 12
        $formattedHeaders = [];
318 12
        foreach ($headers as $name => $value) {
319 12
            $formattedHeaders[] = $this->validateHeaderName($name).': '.$this->validateHeaderValue($value);
320 8
        }
321
322 3
        return $formattedHeaders;
323
    }
324
325
    /**
326
     * Validate Header name
327
     *
328
     * @param string $name
329
     *
330
     * @throws InvalidArgumentException if the header name is invalid
331
     *
332
     * @return string
333
     */
334 12
    protected function validateHeaderName($name)
335
    {
336 12
        if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $name)) {
337 3
            throw new InvalidArgumentException('Invalid header name');
338
        }
339
340 12
        return $name;
341
    }
342
343
    /**
344
     * Validate Header value
345
     *
346
     * @param string $value
347
     *
348
     * @throws InvalidArgumentException if the header value is invalid
349
     *
350
     * @return string
351
     */
352 12
    protected function validateHeaderValue($value)
353
    {
354 12
        if (preg_match("#(?:(?:(?<!\r)\n)|(?:\r(?!\n))|(?:\r\n(?![ \t])))#", $value)
355 12
            || preg_match('/[^\x09\x0a\x0d\x20-\x7E\x80-\xFE]/', $value)
356 8
        ) {
357 6
            throw new InvalidArgumentException('Invalid header value');
358
        }
359
360 12
        return $value;
361
    }
362
363
    /**
364
     * Returns an instance with the specified status.
365
     *
366
     * This method MUST retain the state of the current instance, and return
367
     * an instance that contains the specified status.
368
     *
369
     * @param string $status The status to use with the new instance.
370
     *
371
     * @return static A new instance with the specified status.
372
     */
373 6
    public function withStatus($status)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
374
    {
375 6
        if ($status === $this->status) {
376 3
            return $this;
377
        }
378
379 3
        return new static($status, $this->data, $this->errorMessage, $this->errorCode);
380
    }
381
382
    /**
383
     * Returns an instance with the specified data.
384
     *
385
     * This method MUST retain the state of the current instance, and return
386
     * an instance that contains the specified data.
387
     *
388
     * @param mixed $data The data to use with the new instance.
389
     *
390
     * @return static A new instance with the specified data.
391
     */
392 6
    public function withData($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
393
    {
394 6
        $data = $this->filterData($data);
395 6
        if ($data === $this->data) {
396 3
            return $this;
397
        }
398
399 3
        return new static($this->status, $data, $this->errorMessage, $this->errorCode);
400
    }
401
402
    /**
403
     * Returns an instance with the specified error message and error code.
404
     *
405
     * This method MUST retain the state of the current instance, and return
406
     * an instance that contains the specified error message and error code.
407
     *
408
     * @param string   $errorMessage The error message to use with the new instance.
409
     * @param int|null $errorCode    The error code to use with the new instance.
410
     *
411
     * @return static A new instance with the specified status.
412
     */
413 6
    public function withError($errorMessage, $errorCode = null)
414
    {
415 6
        if ($this->isError() && $errorMessage === $this->errorMessage && $errorCode === $this->errorCode) {
416 3
            return $this;
417
        }
418
419 3
        return new static(static::STATUS_ERROR, $this->data, $errorMessage, $errorCode);
420
    }
421
422
    /**
423
     * Returns a successful JSend object with the specified data
424
     *
425
     * @param mixed $data The data to use with the new instance.
426
     *
427
     * @return static A new succesful instance with the specified data.
428
     */
429 6
    public static function success($data = null)
430
    {
431 6
        return new static(static::STATUS_SUCCESS, $data);
432
    }
433
434
    /**
435
     * Returns a failed JSend object with the specified data
436
     *
437
     * @param mixed $data The data to use with the new instance.
438
     *
439
     * @return static A new failed instance with the specified data.
440
     */
441 3
    public static function fail($data = null)
442
    {
443 3
        return new static(static::STATUS_FAIL, $data);
444
    }
445
446
    /**
447
     * Returns a error JSend object with the specified error message and error code.
448
     *
449
     * @param string   $errorMessage The error message to use with the new instance.
450
     * @param int|null $errorCode    The error code to use with the new instance.
451
     * @param array    $data         The optional data to use with the new instance.
452
     *
453
     * @return static A new failed instance with the specified data.
454
     */
455 9
    public static function error($errorMessage, $errorCode = null, $data = null)
456
    {
457 9
        return new static(static::STATUS_ERROR, $data, $errorMessage, $errorCode);
458
    }
459
460
    /**
461
     * Returns a new instance from a JSON string
462
     *
463
     * @param string $json    The string being decoded
464
     * @param int    $depth   User specified recursion depth.
465
     * @param int    $options Bitmask of JSON decode options
466
     *
467
     * @throws InvalidArgumentException If the string can not be decode
468
     *
469
     * @return static
470
     */
471 6
    public static function createFromString($json, $depth = 512, $options = 0)
472
    {
473 6
        $raw = json_decode($json, true, $depth, $options);
474 6
        if (JSON_ERROR_NONE !== json_last_error()) {
475 3
            throw new InvalidArgumentException(sprintf(
476 3
                'Unable to decode the submitted JSON string: %s',
477 2
                json_last_error_msg()
478 2
            ));
479
        }
480
481 3
        return static::createFromArray($raw);
482
    }
483
484
    /**
485
     * @inheritdoc
486
     */
487 3
    public static function __set_state(array $properties)
488
    {
489 3
        return static::createFromArray($properties);
490
    }
491
492
    /**
493
     * Returns a new instance from an array
494
     *
495
     * @param array $arr The array to build a new JSend object with
496
     *
497
     * @return static
498
     */
499 9
    public static function createFromArray(array $arr)
500
    {
501 9
        $defaultValues = ['status' => null, 'data' => null, 'message' => null, 'code' => null];
502 9
        $arr = array_replace($defaultValues, array_intersect_key($arr, $defaultValues));
503
504 9
        return new static($arr['status'], $arr['data'], $arr['message'], $arr['code']);
505
    }
506
}
507