Failed Conditions
Push — master ( e9d38a...75b13f )
by Arnold
16s
created

Controller::isSuccessful()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 3
nc 3
nop 0
crap 3
1
<?php
2
3
namespace Jasny;
4
5
use Psr\Http\Message\ServerRequestInterface;
6
use Psr\Http\Message\ResponseInterface;
7
8
/**
9
 * Controller
10
 */
11
abstract class Controller
12
{
13
    /**
14
     * Server request
15
     * @var ServerRequestInterface
16
     **/
17
    protected $request = null;
18
19
    /**
20
     * Response
21
     * @var ResponseInterface
22
     **/
23
    protected $response = null;
24
25
    /**
26
     * Common input and output formats with associated MIME
27
     * @var array
28
     */
29
    protected $contentFormats = [
30
        'text/html' => 'html',
31
        'application/json' => 'json',
32
        'application/xml' => 'xml',
33
        'text/xml' => 'xml',
34
        'text/plain' => 'text',
35
        'application/javascript' => 'js',
36
        'text/css' => 'css',
37
        'image/png' => 'png',
38
        'image/gif' => 'gif',
39
        'image/jpeg' => 'jpeg',
40
        'image/x-icon' => 'ico',
41
        'application/x-www-form-urlencoded' => 'post',
42
        'multipart/form-data' => 'post'
43
    ];
44
45
    /**
46
     * Run the controller
47
     *
48
     * @return ResponseInterface
49
     */
50
    abstract public function run();
51
52
    /**
53
     * Get request, set for controller
54
     *
55
     * @return ServerRequestInterface
56
     */
57 1
    public function getRequest()
58
    {
59 1
        return $this->request;
60
    }
61
62
    /**
63
     * Get response. set for controller
64
     *
65
     * @return ResponseInterface
66
     */
67 15
    public function getResponse()
68
    {
69 15
        return $this->response;
70
    }
71
72
    /**
73
     * Run the controller as function
74
     *
75
     * @param ServerRequestInterface $request
76
     * @param ResponseInterface      $response
77
     * @return ResponseInterface
78
     */
79 14
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response)
80
    {
81 14
        $this->request = $request;
82 14
        $this->response = $response;
83
84 14
        return $this->run();
85
    }
86
87
    /**
88
     * Set the headers with HTTP status code and content type.
89
     * @link http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
90
     * 
91
     * Examples:
92
     * <code>
93
     *   $this->responseWith(200, 'json');
94
     *   $this->responseWith(200, 'application/json');
95
     *   $this->responseWith(204);
96
     *   $this->responseWith("204 Created");
97
     *   $this->responseWith('json');
98
     * </code>
99
     * 
100
     * @param int          $code            HTTP status code (may be omitted)
101
     * @param string|array $format          Mime or content format
102
     * @return ResponseInterface $response
103
     */
104 15
    public function responseWith($code, $format = null)
105
    {
106 15
        $response = $this->getResponse();
107
108
        // Shift arguments if $code is omitted
109 15
        if (!is_int($code) && !preg_match('/^\d{3}\b/', $code)) {
110 1
            list($code, $format) = array_merge([null], func_get_args());
111 1
        }
112
113 15
        if ($code) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $code of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
114 14
            $response = $response->withStatus((int)$code);
115 14
        }
116
        
117 15
        if ($format) {
118 9
            $contentType = $this->getContentType($format);
119 9
            $response = $response->withHeader('Content-Type', $contentType);   
120 9
        }
121
122 15
        return $response;
123
    }
124
125
    /**
126
     * Response with success 200 code
127
     *
128
     * @return ResponseInterface $response
129
     */
130 1
    public function ok()
131
    {
132 1
        return $this->responseWith(200);
133
    }
134
135
    /**
136
     * Response with created 201 code, and optionaly redirect to created location
137
     *
138
     * @param string $location              Url of created resource
139
     * @return ResponseInterface $response
140
     */
141 2
    public function created($location = '')
142
    {
143 2
        $response = $this->responseWith(201);
144
145 2
        if ($location) {
146 1
            $response = $response->withHeader('Location', $location);
147 1
        }
148
149 2
        return $response;
150
    }
151
152
    /**
153
     * Response with 204 'No Content'
154
     *
155
     * @return ResponseInterface $response
156
     */
157 1
    public function noContent()
158
    {
159 1
        return $this->responseWith(204);
160
    }
161
162
    /**
163
     * Redirect to url
164
     *
165
     * @param string $url
166
     * @param int $code    301 (Moved Permanently), 303 (See Other) or 307 (Temporary Redirect)
167
     * @return ResponseInterface $response
168
     */
169 6
    public function redirect($url, $code = 303)
170
    {
171 6
        $response = $this->responseWith($code, 'html');
172 6
        $response = $response->withHeader('Location', $url);
173 6
        $response->getBody()->write('You are being redirected to <a href="' . $url . '">' . $url . '</a>');
174
175 6
        return $response;
176
    }
177
178
    /**
179
     * Redirect to previous page, or to home page
180
     *
181
     * @return ResponseInterface $response
182
     */
183 2
    public function back()
184
    {
185 2
        return $this->redirect($this->getLocalReferer() ?: '/');
186
    }
187
188
    /**
189
     * Route to 401
190
     * Note: While the 401 route is used, we don't respond with a 401 http status code.
191
     *
192
     * @return ResponseInterface $response
193
     */
194 2
    public function requireLogin()
195
    {
196 2
        return $this->redirect('/401');
197
    }
198
199
    /**
200
     * Alias of requireLogin
201
     *
202
     * @return ResponseInterface $response
203
     */
204 1
    public function requireAuth()
205
    {
206 1
        return $this->requireLogin();
207
    }
208
209
    /**
210
     * Set response to error 'Bad Request' state
211
     *
212
     * @param string $message
213
     * @param int $code                      HTTP status code
214
     * @return ResponseInterface $response
215
     */
216 2
    public function badRequest($message, $code = 400)
217
    {
218 2
        return $this->error($message, $code);   
219
    }
220
221
    /**
222
     * Set response to error 'Forbidden' state
223
     *
224
     * @param string $message
225
     * @param int $code                      HTTP status code
226
     * @return ResponseInterface $response
227
     */
228 2
    public function forbidden($message, $code = 403)
229
    {
230 2
        return $this->error($message, $code);   
231
    }
232
233
    /**
234
     * Set response to error 'Not Found' state
235
     *
236
     * @param string $message
237
     * @param int $code                      HTTP status code
238
     * @return ResponseInterface $response
239
     */
240 2
    public function notFound($message, $code = 404)
241
    {
242 2
        return $this->error($message, $code);   
243
    }
244
245
    /**
246
     * Set response to error 'Conflict' state
247
     *
248
     * @param string $message
249
     * @param int $code                      HTTP status code
250
     * @return ResponseInterface $response
251
     */
252 2
    public function conflict($message, $code = 409)
253
    {
254 2
        return $this->error($message, $code);   
255
    }
256
257
    /**
258
     * Set response to error 'Too Many Requests' state
259
     *
260
     * @param string $message
261
     * @param int $code                      HTTP status code
262
     * @return ResponseInterface $response
263
     */
264 2
    public function tooManyRequests($message, $code = 429)
265
    {
266 2
        return $this->error($message, $code);   
267
    }
268
269
    /**
270
     * Set response to error state
271
     *
272
     * @param string $message
273
     * @param int $code                     HTTP status code
274
     * @return ResponseInterface $response
275
     */
276 12
    public function error($message, $code = 400)
277
    {        
278 12
        $response = $this->getResponse();
279
280 12
        $errorResponse = $response->withStatus($code);
281 12
        $errorResponse->getBody()->write($message);
282
283 12
        return $errorResponse;
284
    }
285
286
    /**
287
     * Check if response is 2xx succesful, or empty
288
     * 
289
     * @return boolean
290
     */
291 14
    public function isSuccessful()
292
    {
293 14
        $code = $this->getResponseStatusCode();
294
295 14
        return !$code || ($code >= 200 && $code < 300);
296
    }
297
    
298
    /**
299
     * Check if response is a 3xx redirect
300
     * 
301
     * @return boolean
302
     */
303 14
    public function isRedirection()
304
    {
305 14
        $code = $this->getResponseStatusCode();
306
307 14
        return $code >= 300 && $code < 400;
308
    }
309
    
310
    /**
311
     * Check if response is a 4xx client error
312
     * 
313
     * @return boolean
314
     */
315 14
    public function isClientError()
316
    {
317 14
        $code = $this->getResponseStatusCode();
318
319 14
        return $code >= 400 && $code < 500;
320
    }
321
    
322
    /**
323
     * Check if response is a 5xx redirect
324
     * 
325
     * @return boolean
326
     */
327 14
    public function isServerError()
328
    {
329 14
        return $this->getResponseStatusCode() >= 500;
330
    }   
331
332
    /**
333
     * Check if response is 4xx or 5xx error
334
     *
335
     * @return boolean
336
     */
337 13
    public function isError()
338
    {
339 13
        return $this->isClientError() || $this->isServerError();
340
    }
341
342
    /**
343
     * Returns the HTTP referer if it is on the current host
344
     *
345
     * @return string
346
     */
347 4
    public function getLocalReferer()
348
    {
349 4
        $request = $this->getRequest();
350 4
        $referer = $request->getHeaderLine('HTTP_REFERER');
351 4
        $host = $request->getHeaderLine('HTTP_HOST');
352
353 4
        return $referer && parse_url($referer, PHP_URL_HOST) === $host ? $referer : '';
354
    }
355
356
    /**
357
     * Output result
358
     *
359
     * @param mixed $data
360
     * @param string $format 
361
     * @return ResponseInterface $response
362
     */
363 9
    public function output($data, $format)
364
    {
365 9
        $response = $this->getResponse();
366 9
        $contentType = $this->getContentType($format);
367 9
        $response = $response->withHeader('Content-Type', $contentType);
368 9
        $content = is_scalar($data) ? $data : $this->encodeData($data, $format);
369
370 9
        $response->getBody()->write($content);
371
372 9
        return $response;
373
    }
374
375
    /**
376
     * Encode data to send to client
377
     *
378
     * @param mixed $data
379
     * @param string $format
380
     * @return string
381
     */
382 11
    public function encodeData($data, $format)
383
    {
384
        switch ($format) {
385 11
            case 'json': return $this->encodeDataAsJson($data);                            
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
386 4
            case 'xml': return $this->encodeDataAsXml($data);
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
387 2
            case 'html':
388 1
                throw new \InvalidArgumentException("To encode HTML please use a view");                
389 1
            default: 
390 1
                throw new \InvalidArgumentException("Can not encode data for format '$format'");                
391 1
        }
392
    } 
393
394
    /**
395
     * Encode data as xml
396
     *
397
     * @param \SimpleXMLElement $data
398
     * @return string
399
     */
400 2
    protected function encodeDataAsXml(\SimpleXMLElement $data)
401
    {
402 2
        return $data->asXML();
403
    }
404
405
    /**
406
     * Encode data as json
407
     *
408
     * @param mixed
409
     * @return string
410
     */
411 7
    protected function encodeDataAsJson($data)
412
    {
413 7
        $data = json_encode($data);
414
415 7
        return $this->isJsonp() ? 
416 7
            $this->getRequest()->getQueryParams()['callback'] . '(' . $data . ')' : 
417 7
            $data;
418
    }
419
420
    /**
421
     * Check if we should respond with jsonp
422
     *
423
     * @return boolean
424
     */
425 7
    protected function isJsonp()
426
    {
427 7
        $request = $this->getRequest();
428
429 7
        return $request && !empty($request->getQueryParams()['callback']);
430
    }
431
    
432
    /**
433
     * Get status code of response
434
     *
435
     * @return int
436
     */
437 14
    protected function getResponseStatusCode()
438
    {
439 14
       $response = $this->getResponse();
440
       
441 14
       return $response ? $response->getStatusCode() : 0;
442
    }
443
444
    /**
445
     * Get valid content type by simple word description
446
     *
447
     * @param string $format
448
     * @return string
449
     */
450 18
    protected function getContentType($format)
451
    {
452 18
        return array_search($format, $this->contentFormats) ?: $format;
453
    }
454
}
455
456