Completed
Push — feature/0.7.0 ( 362342...b421e8 )
by Ryuichi
02:56
created

Response   C

Complexity

Total Complexity 73

Size/Duplication

Total Lines 586
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 8
Bugs 0 Features 0
Metric Value
c 8
b 0
f 0
dl 0
loc 586
rs 5.5447
wmc 73
lcom 1
cbo 2

35 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 4 1
A setCharset() 0 4 1
A setCacheControl() 0 4 1
A setPragma() 0 4 1
A setType() 0 9 2
A setMimeType() 0 4 1
A setLocation() 0 4 1
A setAccessControlAllowOrigin() 0 7 2
A setXFrameOptions() 0 4 1
A setXXssProtection() 0 4 1
A setStatusCode() 0 11 4
A setContentLength() 0 4 1
A setContentDisposition() 0 6 2
A setContentTransferEncoding() 0 4 1
A setExpires() 0 4 1
A setBody() 0 4 1
A setFile() 0 4 1
A send() 0 5 1
D header() 0 66 10
A body() 0 12 2
A movePermanently() 0 6 1
A badRequest() 0 4 1
A unauthorized() 0 4 1
A forbidden() 0 4 1
A notFound() 0 4 1
A methodNotAllowed() 0 4 1
A unprocessableEntity() 0 4 1
A internalServerError() 0 4 1
A displayFile() 0 7 2
A downloadFile() 0 14 4
A move() 0 12 3
A start() 0 5 1
D end() 0 32 17
A clean() 0 4 1
A bodyTemplate() 0 16 1

How to fix   Complexity   

Complex Class

Complex classes like Response 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 Response, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace WebStream\Http;
3
4
use WebStream\DI\Injector;
5
use WebStream\Module\Utility\CommonUtils;
6
7
/**
8
 * Response
9
 * @author Ryuichi TANAKA.
10
 * @since 2012/12/19
11
 * @version 0.7
12
 */
13
class Response
14
{
15
    use Injector, CommonUtils;
16
17
    /** HTTPバージョン */
18
    const HTTP_VERSION = '1.1';
19
    /** 文字コード */
20
    private $charset = 'UTF-8';
21
    /** Cache-Control */
22
    private $cacheControl = 'no-cache';
23
    /** Pragma */
24
    private $pragma = 'no-cache';
25
    /** Mime type */
26
    private $mimeType = 'text/html';
27
    /** ロケーション */
28
    private $location;
29
    /** Access-Control-Allow-Origin */
30
    private $accessControlAllowOrigin = [];
31
    /** X-Frame-Options */
32
    private $xframeOptions = 'SAMEORIGIN';
33
    /** X-XSS-Protection */
34
    private $xxssProtection = '1; mode=block';
35
    /** ステータスコード */
36
    private $statusCode = 200;
37
    /** レスポンスボディ */
38
    private $body;
39
    /** レスポンスファイル */
40
    private $file;
41
    /** Content-Length */
42
    private $contentLength;
43
    /** Content-Disposition */
44
    private $contentDisposition;
45
    /** Content-Transfer-Encoding */
46
    private $contentTransferEncoding;
47
    /** Expires */
48
    private $expires;
49
50
    /**
51
     * デストラクタ
52
     */
53
    public function __destruct()
54
    {
55
        $this->logger->debug("Response is clear.");
0 ignored issues
show
Documentation introduced by
The property logger does not exist on object<WebStream\Http\Response>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
56
    }
57
58
    /**
59
     * 文字コードを設定
60
     * @param String 文字コード
61
     */
62
    public function setCharset($charset)
63
    {
64
        $this->charset = $charset;
65
    }
66
67
    /**
68
     * Cache-Controlを設定
69
     * @param String Cache-Control
70
     */
71
    public function setCacheControl($cacheControl)
72
    {
73
        $this->cacheControl = $cacheControl;
74
    }
75
76
    /**
77
     * Pragmaを設定
78
     * @param String Pragma
79
     */
80
    public function setPragma($pragma)
81
    {
82
        $this->pragma = $pragma;
83
    }
84
85
    /**
86
     * MimeTypeを設定
87
     * ファイルタイプにより指定
88
     * @param String ファイルタイプ
89
     */
90
    public function setType($fileType)
91
    {
92
        if (array_key_exists($fileType, $this->mime)) {
93
            $this->mimeType = $this->mime[$fileType];
94
        } else {
95
            // 不明なファイルが指定された場合、画面に表示させずダウンロードさせる
96
            $this->mimeType = $this->mime['file'];
97
        }
98
    }
99
100
    /**
101
     * MimeTypeを設定
102
     * MimeTypeを直接指定
103
     * @param String MimeType
104
     */
105
    public function setMimeType($mimeType)
106
    {
107
        $this->mimeType = $mimeType;
108
    }
109
110
    /**
111
     * リダイレクトロケーションを設定
112
     * @param String ロケーションパス
113
     */
114
    public function setLocation($location)
115
    {
116
        $this->location = $location;
117
    }
118
119
    /**
120
     * Access-Control-Allow-Originを設定
121
     * 複数指定する場合は、引数に列挙する
122
     * @param String URLまたはワイルドカード
123
     */
124
    public function setAccessControlAllowOrigin()
125
    {
126
        $arguments = func_get_args();
127
        foreach ($arguments as $argument) {
128
            $this->accessControlAllowOrigin[] = $argument;
129
        }
130
    }
131
132
    /**
133
     * X-Frame-Optionsを設定
134
     * @param String SAMEORIGINまたはDENY
135
     */
136
    public function setXFrameOptions($xframeOptions)
137
    {
138
        $this->xframeOptions = $xframeOptions;
139
    }
140
141
    /**
142
     * X-XSS-Protectionを設定
143
     * @param String XSSフィルタ設定(0:無効、1:有効)
144
     */
145
    public function setXXssProtection($xxssProtection)
146
    {
147
        $this->xxssProtection = $xxssProtection;
148
    }
149
150
    /**
151
     * ステータスコードを設定
152
     * @param Integer ステータスコード
153
     */
154
    public function setStatusCode($statusCode)
155
    {
156
        if (!is_string($statusCode) && !is_int($statusCode)) {
157
            throw new ConnectionException("Invalid status code format: " . strval($statusCode));
158
        }
159
160
        if (!array_key_exists($statusCode, $this->status)) {
161
            throw new ConnectionException("Unknown status code: " . $statusCode);
162
        }
163
        $this->statusCode = $statusCode;
0 ignored issues
show
Documentation Bug introduced by
It seems like $statusCode can also be of type string. However, the property $statusCode is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
164
    }
165
166
    /**
167
     * Content-Lengthを設定
168
     * @param Integer Content-Length
169
     */
170
    public function setContentLength($contentLength)
171
    {
172
        $this->contentLength = $contentLength;
173
    }
174
175
    /**
176
     * Content-Dispositionを設定
177
     * @param String ファイル名
178
     */
179
    public function setContentDisposition($filename)
180
    {
181
        if (file_exists($filename)) {
182
            $this->contentDisposition = 'attachement; filename="'. basename($filename) . '"';
183
        }
184
    }
185
186
    /**
187
     * Content-Transfer-Encodingを設定
188
     * @param String エンコーディング方法
189
     */
190
    public function setContentTransferEncoding($contentTransferEncoding)
191
    {
192
        $this->contentTransferEncoding = $contentTransferEncoding;
193
    }
194
195
    /**
196
     * Expiresを設定
197
     * @param Integer 有効期限
198
     */
199
    public function setExpires($expires)
200
    {
201
        $this->expires = $expires;
202
    }
203
204
    /**
205
     * レスポンスボディを設定
206
     * @param String レスポンスボディ
207
     */
208
    public function setBody($body)
209
    {
210
        $this->body = $body;
211
    }
212
213
    /**
214
     * レスポンスファイルを設定
215
     * @param String レスポンスファイル
216
     */
217
    public function setFile($file)
218
    {
219
        $this->file = $file;
220
    }
221
222
    /**
223
     * レスポンスを送出する
224
     */
225
    public function send()
226
    {
227
        $this->header();
228
        $this->body();
229
    }
230
231
    /**
232
     * レスポンスヘッダを送出する
233
     */
234
    public function header()
235
    {
236
        if (headers_sent()) {
237
            return;
238
        }
239
240
        // StatusCode
241
        $headerMessage = 'HTTP/' . self::HTTP_VERSION . ' ' .
242
                         $this->statusCode . ' ' . $this->status[$this->statusCode];
243
        header($headerMessage);
244
245
        // Redirect
246
        if (intval($this->statusCode) === 301) {
247
            header('Location: ' . $this->location);
248
        }
249
250
        // Content-Type
251
        header('Content-Type: ' . $this->mimeType . '; charset=' . $this->charset);
252
253
        // Content-Length
254
        if ($this->contentLength === null) {
255
            $this->contentLength = $this->bytelen($this->body);
256
        }
257
        header('Content-Length: ' . $this->contentLength);
258
259
        // Content-Disposition
260
        if ($this->contentDisposition !== null) {
261
            header('Content-Disposition: ' . $this->contentDisposition);
262
        }
263
264
        // Content-Transfer-Encoding
265
        if ($this->contentTransferEncoding !== null) {
266
            header('Content-Transfer-Encoding: ' . $this->contentTransferEncoding);
267
        }
268
269
        // Cache-Control
270
        header('Cache-Control: ' . $this->cacheControl);
271
272
        // Pragma
273
        header('Pragma: ' . $this->pragma);
274
275
        // Expires
276
        if ($this->expires !== null) {
277
            header('Expires: ' . $this->expires);
278
        }
279
280
        // X-Content-Type-Options
281
        header("X-Content-Type-Options: nosniff");
282
283
        // Access-Control-Allow-Origin
284
        if (!empty($this->accessControlAllowOrigin)) {
285
            header('Access-Control-Allow-Origin: ' . implode(',', $this->accessControlAllowOrigin));
286
        }
287
288
        // X-Frame-Options
289
        if ($this->xframeOptions !== null) {
290
            header('X-Frame-Options: ' . $this->xframeOptions);
291
        }
292
293
        // X-XSS-Protection
294
        if ($this->xxssProtection !== null) {
295
            header('X-XSS-Protection: ' . $this->xxssProtection);
296
        }
297
298
        $this->logger->info("HTTP access occured: status code " . $this->statusCode);
0 ignored issues
show
Documentation introduced by
The property logger does not exist on object<WebStream\Http\Response>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
299
    }
300
301
    /**
302
     * レスポンスボディを送出する
303
     */
304
    public function body()
305
    {
306
        if ($this->file !== null) {
307
            // バイナリ系、その他のファイルはダウンロードする
308
            ob_clean();
309
            flush();
310
            readfile($this->file);
311
        } else {
312
            // テキスト系は画面に表示する
313
            echo $this->body;
314
        }
315
    }
316
317
    /**
318
     * Mime-Type
319
     */
320
    protected $mime = [
321
        'txt'   => 'text/plain',
322
        'jpeg'  => 'image/jpeg',
323
        'jpg'   => 'image/jpeg',
324
        'gif'   => 'image/gif',
325
        'png'   => 'image/png',
326
        'tiff'  => 'image/tiff',
327
        'tif'   => 'image/tiff',
328
        'bmp'   => 'image/bmp',
329
        'ico'   => 'image/x-icon',
330
        'svg'   => 'image/svg+xml',
331
        'xml'   => 'application/xml',
332
        'xsl'   => 'application/xml',
333
        'rss'   => 'application/rss+xml',
334
        'rdf'   => 'application/rdf+xml',
335
        'atom'  => 'application/atom+xml',
336
        'zip'   => 'application/zip',
337
        'html'  => 'text/html',
338
        'htm'   => 'text/html',
339
        'css'   => 'text/css',
340
        'csv'   => 'text/csv',
341
        'tsv'   => 'text/tab-separated-values',
342
        'js'    => 'text/javascript',
343
        'jsonp' => 'text/javascript',
344
        'json'  => 'application/json',
345
        'pdf'   => 'application/pdf',
346
        'file'  => 'application/octet-stream'
347
    ];
348
349
    /**
350
     * Status
351
     */
352
    protected $status = [
353
        '100' => 'Continue',
354
        '101' => 'Switching Protocols',
355
        '102' => 'Processing',
356
        '200' => 'OK',
357
        '201' => 'Created',
358
        '202' => 'Accepted',
359
        '203' => 'Non-Authoritative Information',
360
        '204' => 'No Content',
361
        '205' => 'Reset Content',
362
        '206' => 'Partial Content',
363
        '207' => 'Multi-Status',
364
        '208' => 'Already Reported',
365
        '226' => 'IM Used',
366
        '300' => 'Multiple Choices',
367
        '301' => 'Moved Permanently',
368
        '302' => 'Found',
369
        '303' => 'See Other',
370
        '304' => 'Not Modified',
371
        '305' => 'Use Proxy',
372
        '307' => 'Temporary Redirect',
373
        '400' => 'Bad Request',
374
        '401' => 'Unauthorized',
375
        '402' => 'Payment Required',
376
        '403' => 'Forbidden',
377
        '404' => 'Not Found',
378
        '405' => 'Method Not Allowed',
379
        '406' => 'Not Acceptable',
380
        '407' => 'Proxy Authentication Required',
381
        '408' => 'Request Timeout',
382
        '409' => 'Conflict',
383
        '410' => 'Gone',
384
        '411' => 'Length Required',
385
        '412' => 'Precondition Failed',
386
        '413' => 'Request Entity Too Large',
387
        '414' => 'Request-URI Too Long',
388
        '415' => 'Unsupported Media Type',
389
        '416' => 'Requested Range Not Satisfiable',
390
        '417' => 'Expectation Failed',
391
        '418' => "I'm a teapot",
392
        '422' => 'Unprocessable Entity',
393
        '423' => 'Locked',
394
        '424' => 'Failed Dependency',
395
        '426' => 'Upgrade Required',
396
        '500' => 'Internal Server Error',
397
        '501' => 'Not Implemented',
398
        '502' => 'Bad Gateway',
399
        '503' => 'Service Unavailable',
400
        '504' => 'Gateway Timeout',
401
        '505' => 'HTTP Version Not Supported',
402
        '506' => 'Variant Also Negotiates',
403
        '507' => 'Insufficient Storage',
404
        '508' => 'Loop Detected',
405
        '510' => 'Not Extended',
406
    ];
407
408
    /**
409
     * 301 alias
410
     * @param String redirect url
411
     */
412
    public function movePermanently($url)
413
    {
414
        $this->setLocation($url);
415
        $this->setStatusCode(301);
416
        $this->send();
417
    }
418
419
    /**
420
     * 400 alias
421
     */
422
    public function badRequest()
423
    {
424
        $this->move(400);
425
    }
426
427
    /**
428
     * 401 alias
429
     */
430
    public function unauthorized()
431
    {
432
        $this->move(401);
433
    }
434
435
    /**
436
     * 403 alias
437
     */
438
    public function forbidden()
439
    {
440
        $this->move(403);
441
    }
442
443
    /**
444
     * 404 alias
445
     */
446
    public function notFound()
447
    {
448
        $this->move(404);
449
    }
450
451
    /**
452
     * 405 alias
453
     */
454
    public function methodNotAllowed()
455
    {
456
        $this->move(405);
457
    }
458
459
    /**
460
     * 422 alias
461
     */
462
    public function unprocessableEntity()
463
    {
464
        $this->move(422);
465
    }
466
467
    /**
468
     * 500 alias
469
     */
470
    public function internalServerError()
471
    {
472
        $this->move(500);
473
    }
474
475
    /**
476
     * 静的ファイルを表示
477
     * @param String ファイル名
478
     */
479
    public function displayFile($filename)
480
    {
481
        $type = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
482
        $this->setType($type);
483
        $this->setContentLength(file_exists($filename) ? filesize($filename) : 0);
484
        $this->setFile($filename);
485
    }
486
487
    /**
488
     * ファイルをダウンロード
489
     * @param String ファイル名
490
     * @param String ユーザエージェント
491
     */
492
    public function downloadFile($filename, $ua)
493
    {
494
        $type = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
495
        $this->setType($type);
496
        $this->setContentLength(file_exists($filename) ? filesize($filename) : 0);
497
        $this->setContentDisposition($filename);
498
        $this->setExpires(0);
499
        $this->setContentTransferEncoding('binary');
500
        if (isset($ua) && strpos($ua, 'MSIE')) {
501
            $this->setCacheControl('must-revalidate, post-check=0, pre-check=0');
502
            $this->setPragma('public');
503
        }
504
        $this->setFile($filename);
505
    }
506
507
    /**
508
     * 指定したステータスコードのページに遷移
509
     * @param Integer ステータスコード
510
     */
511
    public function move($statusCode)
512
    {
513
        if (ob_get_contents()) {
514
            ob_clean();
515
        }
516
        $statusCode = array_key_exists($statusCode, $this->status) ? $statusCode : 500;
517
        $this->setStatusCode($statusCode);
518
        $bodyMessage = $statusCode . ' ' . $this->status[$statusCode];
519
        $this->setBody($this->bodyTemplate($bodyMessage));
520
        $this->send();
521
        exit;
522
    }
523
524
    /**
525
     * レスポンス送出を開始する
526
     */
527
    public function start()
528
    {
529
        ob_start();
530
        ob_implicit_flush(false);
531
    }
532
533
    /**
534
     * レスポンスを送出して終了する
535
     */
536
    public function end()
537
    {
538
        $body = "";
539
        if (($error = error_get_last()) === null) {
540
            $body = ob_get_clean();
541
        } else {
542
            switch ($error['type']) {
543
                case E_ERROR:
544
                case E_CORE_ERROR:
545
                case E_COMPILE_ERROR:
546
                case E_USER_ERROR:
547
                case E_RECOVERABLE_ERROR:
548
                case E_PARSE:
549
                    $this->clean();
550
                    break;
551
                case E_WARNING:
552
                case E_CORE_WARNING:
553
                case E_COMPILE_WARNING:
554
                case E_USER_WARNING:
555
                case E_STRICT:
556
                case E_NOTICE:
557
                case E_USER_NOTICE:
558
                case E_DEPRECATED:
559
                case E_USER_DEPRECATED:
560
                    $body = ob_get_clean();
561
                    break;
562
            }
563
        }
564
565
        $this->setBody($body);
566
        $this->send();
567
    }
568
569
    /**
570
     * レスポンス送出せず終了する
571
     */
572
    public function clean()
573
    {
574
        ob_end_clean();
575
    }
576
577
    /**
578
     * HTMLテンプレート
579
     * @param String 表示内容
580
     * @return String HTML
581
     */
582
    private function bodyTemplate($content)
583
    {
584
        return <<< HTML
585
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
586
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
587
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
588
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
589
    <head>
590
        <title>$content</title>
591
    </head>
592
    <body>
593
        <h1>$content</h1>
594
    </body>
595
</html>
596
HTML;
597
    }
598
}
599