Passed
Push — master ( 66eae1...74e494 )
by
unknown
01:37
created

Response   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 368
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 83.75%

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 6
dl 0
loc 368
ccs 67
cts 80
cp 0.8375
rs 10
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getStatusCode() 0 4 1
A getReasonPhrase() 0 10 3
A withStatus() 0 7 1
A withArray() 0 9 1
A withItem() 0 14 2
A withCollection() 0 18 3
A withError() 0 19 1
A errorForbidden() 0 4 1
A errorInternalError() 0 4 1
A errorNotFound() 0 4 1
A errorUnauthorized() 0 4 1
A errorWrongArgs() 0 4 1
A errorGone() 0 4 1
A errorMethodNotAllowed() 0 4 1
A errorUnwillingToProcess() 0 4 1
A errorUnprocessable() 0 4 1
A setStatusCode() 0 14 4
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: harry
5
 * Date: 2/14/18
6
 * Time: 11:58 AM
7
 */
8
9
namespace PhpRestfulApiResponse;
10
11
use League\Fractal\Manager;
12
use League\Fractal\Pagination\Cursor;
13
use League\Fractal\Resource\Collection;
14
use League\Fractal\Resource\Item;
15
use League\Fractal\TransformerAbstract;
16
use PhpRestfulApiResponse\Contracts\PhpRestfulApiResponse;
17
use Zend\Diactoros\MessageTrait;
18
use InvalidArgumentException;
19
20
class Response implements PhpRestfulApiResponse
21
{
22
    use MessageTrait;
23
24
    const MIN_STATUS_CODE_VALUE = 100;
25
    const MAX_STATUS_CODE_VALUE = 599;
26
27
    /**
28
     * Map of standard HTTP status code/reason phrases
29
     *
30
     * @var array
31
     */
32
    private $phrases = [
33
        // INFORMATIONAL CODES
34
        100 => 'Continue',
35
        101 => 'Switching Protocols',
36
        102 => 'Processing',
37
        103 => 'Early Hints',
38
        // SUCCESS CODES
39
        200 => 'OK',
40
        201 => 'Created',
41
        202 => 'Accepted',
42
        203 => 'Non-Authoritative Information',
43
        204 => 'No Content',
44
        205 => 'Reset Content',
45
        206 => 'Partial Content',
46
        207 => 'Multi-Status',
47
        208 => 'Already Reported',
48
        226 => 'IM Used',
49
        // REDIRECTION CODES
50
        300 => 'Multiple Choices',
51
        301 => 'Moved Permanently',
52
        302 => 'Found',
53
        303 => 'See Other',
54
        304 => 'Not Modified',
55
        305 => 'Use Proxy',
56
        306 => 'Switch Proxy', // Deprecated to 306 => '(Unused)'
57
        307 => 'Temporary Redirect',
58
        308 => 'Permanent Redirect',
59
        // CLIENT ERROR
60
        400 => 'Bad Request',
61
        401 => 'Unauthorized',
62
        402 => 'Payment Required',
63
        403 => 'Forbidden',
64
        404 => 'Not Found',
65
        405 => 'Method Not Allowed',
66
        406 => 'Not Acceptable',
67
        407 => 'Proxy Authentication Required',
68
        408 => 'Request Timeout',
69
        409 => 'Conflict',
70
        410 => 'Gone',
71
        411 => 'Length Required',
72
        412 => 'Precondition Failed',
73
        413 => 'Payload Too Large',
74
        414 => 'URI Too Long',
75
        415 => 'Unsupported Media Type',
76
        416 => 'Range Not Satisfiable',
77
        417 => 'Expectation Failed',
78
        418 => 'I\'m a teapot',
79
        421 => 'Misdirected Request',
80
        422 => 'Unprocessable Entity',
81
        423 => 'Locked',
82
        424 => 'Failed Dependency',
83
        425 => 'Unordered Collection',
84
        426 => 'Upgrade Required',
85
        428 => 'Precondition Required',
86
        429 => 'Too Many Requests',
87
        431 => 'Request Header Fields Too Large',
88
        444 => 'Connection Closed Without Response',
89
        451 => 'Unavailable For Legal Reasons',
90
        // SERVER ERROR
91
        499 => 'Client Closed Request',
92
        500 => 'Internal Server Error',
93
        501 => 'Not Implemented',
94
        502 => 'Bad Gateway',
95
        503 => 'Service Unavailable',
96
        504 => 'Gateway Timeout',
97
        505 => 'HTTP Version Not Supported',
98
        506 => 'Variant Also Negotiates',
99
        507 => 'Insufficient Storage',
100
        508 => 'Loop Detected',
101
        510 => 'Not Extended',
102
        511 => 'Network Authentication Required',
103
        599 => 'Network Connect Timeout Error',
104
    ];
105
106
    /**
107
     * @var string
108
     */
109
    private $reasonPhrase = '';
110
111
    /**
112
     * @var int
113
     */
114
    private $statusCode;
115
116
    /**
117
     * Response constructor.
118
     * @param string $body
119
     * @param int $status
120
     * @param array $headers
121
     */
122 21
    public function __construct($body = 'php://memory', $status = 200, array $headers = [])
123
    {
124 21
        $this->setStatusCode($status);
125 21
        $this->stream = $this->getStream($body, 'wb+');
126 21
        $this->setHeaders($headers);
127 21
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132 21
    public function getStatusCode()
133
    {
134 21
        return $this->statusCode;
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140 18
    public function getReasonPhrase()
141
    {
142 18
        if (! $this->reasonPhrase
143 18
            && isset($this->phrases[$this->statusCode])
144
        ) {
145 18
            $this->reasonPhrase = $this->phrases[$this->statusCode];
146
        }
147
148 18
        return $this->reasonPhrase;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154
    public function withStatus($code, $reasonPhrase = '')
155
    {
156
        $new = clone $this;
157
        $new->setStatusCode($code);
158
        $new->reasonPhrase = $reasonPhrase;
159
        return $new;
160
    }
161
162
    /**
163
     * @param array|null $data
164
     * @param $code
165
     * @param array $headers
166
     * @return Response|static
167
     */
168 3
    public function withArray($data, $code = 200, array $headers = [])
169
    {
170 3
        $new = clone $this;
171 3
        $new->setStatusCode($code);
172 3
        $new->getBody()->write(json_encode($data));
173 3
        $new = $new->withHeader('Content-Type', 'application/json');
174 3
        $new->headers = array_merge($new->headers, $headers);
175 3
        return $new;
176
    }
177
178
    /**
179
     * @param $data
180
     * @param TransformerAbstract|callable $transformer
181
     * @param int $code
182
     * @param null $resourceKey
183
     * @param array $meta
184
     * @param array $headers
185
     * @return Response
186
     */
187 1
    public function withItem($data, $transformer, $code = 200, $resourceKey = null, $meta = [], array $headers = [])
188
    {
189 1
        $resource = new Item($data, $transformer, $resourceKey);
190
191 1
        foreach ($meta as $metaKey => $metaValue) {
192
            $resource->setMetaValue($metaKey, $metaValue);
193
        }
194
195 1
        $manager = new Manager();
196
197 1
        $rootScope = $manager->createData($resource);
198
199 1
        return $this->withArray($rootScope->toArray(), $code, $headers);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->withArray(...ay(), $code, $headers); (PhpRestfulApiResponse\Response) is incompatible with the return type declared by the interface PhpRestfulApiResponse\Co...ulApiResponse::withItem of type Zend\Diactoros\Response.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
200
    }
201
202
    /**
203
     * @param $data
204
     * @param TransformerAbstract|callable $transformer
205
     * @param int $code
206
     * @param null $resourceKey
207
     * @param Cursor|null $cursor
208
     * @param array $meta
209
     * @param array $headers
210
     * @return Response
211
     */
212 1
    public function withCollection($data, $transformer, $code = 200, $resourceKey = null, Cursor $cursor = null, $meta = [], array $headers = [])
213
    {
214 1
        $resource = new Collection($data, $transformer, $resourceKey);
215
216 1
        foreach ($meta as $metaKey => $metaValue) {
217
            $resource->setMetaValue($metaKey, $metaValue);
218
        }
219
220 1
        if (!is_null($cursor)) {
221
            $resource->setCursor($cursor);
222
        }
223
224 1
        $manager = new Manager();
225
226 1
        $rootScope = $manager->createData($resource);
227
228 1
        return $this->withArray($rootScope->toArray(), $code, $headers);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->withArray(...ay(), $code, $headers); (PhpRestfulApiResponse\Response) is incompatible with the return type declared by the interface PhpRestfulApiResponse\Co...esponse::withCollection of type Zend\Diactoros\Response.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
229
    }
230
231
    /**
232
     * Response for errors
233
     *
234
     * @param string|array $message
235
     * @param string $code
236
     * @param array  $headers
237
     * @return mixed
238
     */
239 18
    public function withError($message, $code, array $headers = [])
240
    {
241 18
        $new = clone $this;
242 18
        $new->setStatusCode($code);
243 18
        $new->getBody()->write(
244 18
            json_encode(
245
                [
246 18
                    'error' => array_filter([
247 18
                        'http_code' => $new->statusCode,
248 18
                        'phrase' => $new->getReasonPhrase(),
249 18
                        'message' => $message
250
                    ])
251
                ]
252
            )
253
        );
254 18
        $new = $new->withHeader('Content-Type', 'application/json');
255 18
        $new->headers = array_merge($new->headers, $headers);
256 18
        return $new;
257
    }
258
259
    /**
260
     * Generates a response with a 403 HTTP header and a given message.
261
     *
262
     * @param string $message
263
     * @param array  $headers
264
     * @return mixed
265
     */
266 2
    public function errorForbidden(string $message = '', array $headers = [])
267
    {
268 2
        return $this->withError($message, 403, $headers);
269
    }
270
271
    /**
272
     * Generates a response with a 500 HTTP header and a given message.
273
     *
274
     * @param string $message
275
     * @param array  $headers
276
     * @return mixed
277
     */
278 2
    public function errorInternalError(string $message = '', array $headers = [])
279
    {
280 2
        return $this->withError($message, 500, $headers);
281
    }
282
283
    /**
284
     * Generates a response with a 404 HTTP header and a given message.
285
     *
286
     * @param string $message
287
     * @param array  $headers
288
     * @return mixed
289
     */
290 2
    public function errorNotFound(string $message = '', array $headers = [])
291
    {
292 2
        return $this->withError($message, 404, $headers);
293
    }
294
295
    /**
296
     * Generates a response with a 401 HTTP header and a given message.
297
     *
298
     * @param string $message
299
     * @param array $headers
300
     * @return mixed
301
     */
302 2
    public function errorUnauthorized(string $message = '', array $headers = [])
303
    {
304 2
        return $this->withError($message, 401, $headers);
305
    }
306
307
    /**
308
     * Generates a response with a 400 HTTP header and a given message.
309
     *
310
     * @param array $message
311
     * @param array $headers
312
     * @return mixed
313
     */
314 1
    public function errorWrongArgs(array $message, array $headers = [])
315
    {
316 1
        return $this->withError($message, 400, $headers);
317
    }
318
319
    /**
320
     * Generates a response with a 410 HTTP header and a given message.
321
     *
322
     * @param string $message
323
     * @param array $headers
324
     * @return mixed
325
     */
326 2
    public function errorGone(string $message = '', array $headers = [])
327
    {
328 2
        return $this->withError($message, 410, $headers);
329
    }
330
331
    /**
332
     * Generates a response with a 405 HTTP header and a given message.
333
     *
334
     * @param string $message
335
     * @param array $headers
336
     * @return mixed
337
     */
338 2
    public function errorMethodNotAllowed(string $message = '', array $headers = [])
339
    {
340 2
        return $this->withError($message, 405, $headers);
341
    }
342
343
    /**
344
     * Generates a Response with a 431 HTTP header and a given message.
345
     *
346
     * @param string $message
347
     * @param array $headers
348
     * @return mixed
349
     */
350 2
    public function errorUnwillingToProcess(string $message = '', array $headers = [])
351
    {
352 2
        return $this->withError($message, 431, $headers);
353
    }
354
355
    /**
356
     * Generates a Response with a 422 HTTP header and a given message.
357
     *
358
     * @param string $message
359
     * @param array $headers
360
     * @return mixed
361
     */
362 2
    public function errorUnprocessable(string $message = '', array $headers = [])
363
    {
364 2
        return $this->withError($message, 422, $headers);
365
    }
366
367
    /**
368
     * Set a valid status code.
369
     *
370
     * @param int $code
371
     * @throws InvalidArgumentException on an invalid status code.
372
     */
373 21
    private function setStatusCode(int $code)
374
    {
375 21
        if ($code < static::MIN_STATUS_CODE_VALUE
376 21
            || $code > static::MAX_STATUS_CODE_VALUE
377
        ) {
378
            throw new InvalidArgumentException(sprintf(
379
                'Invalid status code "%s"; must be an integer between %d and %d, inclusive',
380
                (is_scalar($code) ? $code : gettype($code)),
381
                static::MIN_STATUS_CODE_VALUE,
382
                static::MAX_STATUS_CODE_VALUE
383
            ));
384
        }
385 21
        $this->statusCode = $code;
386
    }
387
}