Response::end()   C
last analyzed

Complexity

Conditions 17
Paths 17

Size

Total Lines 31
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 26
nc 17
nop 0
dl 0
loc 31
rs 5.2166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace WebStream\Http;
3
4
use WebStream\DI\Injector;
5
use WebStream\Util\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
Bug introduced by
The method debug() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

55
        $this->logger->/** @scrutinizer ignore-call */ 
56
                       debug("Response is clear.");

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug Best Practice introduced by
The property logger does not exist on WebStream\Http\Response. Since you implemented __get, consider adding a @property annotation.
Loading history...
56
    }
57
58
    /**
59
     * 文字コードを設定
60
     * @param String 文字コード
0 ignored issues
show
Documentation Bug introduced by
The doc comment 文字コード at position 0 could not be parsed: Unknown type name '文字コード' at position 0 in 文字コード.
Loading history...
61
     */
62
    public function setCharset($charset)
63
    {
64
        $this->charset = $charset;
65
    }
66
67
    /**
68
     * Cache-Controlを設定
69
     * @param String Cache-Control
0 ignored issues
show
Documentation Bug introduced by
The doc comment Cache-Control at position 0 could not be parsed: Unknown type name 'Cache-Control' at position 0 in Cache-Control.
Loading history...
70
     */
71
    public function setCacheControl($cacheControl)
72
    {
73
        $this->cacheControl = $cacheControl;
74
    }
75
76
    /**
77
     * Pragmaを設定
78
     * @param String Pragma
0 ignored issues
show
Bug introduced by
The type WebStream\Http\Pragma was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
79
     */
80
    public function setPragma($pragma)
81
    {
82
        $this->pragma = $pragma;
83
    }
84
85
    /**
86
     * MimeTypeを設定
87
     * ファイルタイプにより指定
88
     * @param String ファイルタイプ
0 ignored issues
show
Documentation Bug introduced by
The doc comment ファイルタイプ at position 0 could not be parsed: Unknown type name 'ファイルタイプ' at position 0 in ファイルタイプ.
Loading history...
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
0 ignored issues
show
Bug introduced by
The type WebStream\Http\MimeType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
104
     */
105
    public function setMimeType($mimeType)
106
    {
107
        $this->mimeType = $mimeType;
108
    }
109
110
    /**
111
     * リダイレクトロケーションを設定
112
     * @param String ロケーションパス
0 ignored issues
show
Documentation Bug introduced by
The doc comment ロケーションパス at position 0 could not be parsed: Unknown type name 'ロケーションパス' at position 0 in ロケーションパス.
Loading history...
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
0 ignored issues
show
Documentation Bug introduced by
The doc comment SAMEORIGINまたはDENY at position 0 could not be parsed: Unknown type name 'SAMEORIGINまたはDENY' at position 0 in SAMEORIGINまたはDENY.
Loading history...
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 ステータスコード
0 ignored issues
show
Documentation Bug introduced by
The doc comment ステータスコード at position 0 could not be parsed: Unknown type name 'ステータスコード' at position 0 in ステータスコード.
Loading history...
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));
0 ignored issues
show
Bug introduced by
The type WebStream\Http\ConnectionException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
158
        }
159
160
        if (!array_key_exists($statusCode, $this->status)) {
161
            throw new ConnectionException("Unknown status code: " . $statusCode);
162
        }
163
        $this->statusCode = $statusCode;
164
    }
165
166
    /**
167
     * Content-Lengthを設定
168
     * @param Integer Content-Length
0 ignored issues
show
Documentation Bug introduced by
The doc comment Content-Length at position 0 could not be parsed: Unknown type name 'Content-Length' at position 0 in Content-Length.
Loading history...
169
     */
170
    public function setContentLength($contentLength)
171
    {
172
        $this->contentLength = $contentLength;
173
    }
174
175
    /**
176
     * Content-Dispositionを設定
177
     * @param String ファイル名
0 ignored issues
show
Documentation Bug introduced by
The doc comment ファイル名 at position 0 could not be parsed: Unknown type name 'ファイル名' at position 0 in ファイル名.
Loading history...
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 エンコーディング方法
0 ignored issues
show
Documentation Bug introduced by
The doc comment エンコーディング方法 at position 0 could not be parsed: Unknown type name 'エンコーディング方法' at position 0 in エンコーディング方法.
Loading history...
189
     */
190
    public function setContentTransferEncoding($contentTransferEncoding)
191
    {
192
        $this->contentTransferEncoding = $contentTransferEncoding;
193
    }
194
195
    /**
196
     * Expiresを設定
197
     * @param Integer 有効期限
0 ignored issues
show
Documentation Bug introduced by
The doc comment 有効期限 at position 0 could not be parsed: Unknown type name '有効期限' at position 0 in 有効期限.
Loading history...
198
     */
199
    public function setExpires($expires)
200
    {
201
        $this->expires = $expires;
202
    }
203
204
    /**
205
     * レスポンスボディを設定
206
     * @param String レスポンスボディ
0 ignored issues
show
Documentation Bug introduced by
The doc comment レスポンスボディ at position 0 could not be parsed: Unknown type name 'レスポンスボディ' at position 0 in レスポンスボディ.
Loading history...
207
     */
208
    public function setBody($body)
209
    {
210
        $this->body = $body;
211
    }
212
213
    /**
214
     * レスポンスファイルを設定
215
     * @param String レスポンスファイル
0 ignored issues
show
Documentation Bug introduced by
The doc comment レスポンスファイル at position 0 could not be parsed: Unknown type name 'レスポンスファイル' at position 0 in レスポンスファイル.
Loading history...
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
Bug Best Practice introduced by
The property logger does not exist on WebStream\Http\Response. Since you implemented __get, consider adding a @property annotation.
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
0 ignored issues
show
Bug introduced by
The type WebStream\Http\redirect was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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 ファイル名
0 ignored issues
show
Documentation Bug introduced by
The doc comment ファイル名 at position 0 could not be parsed: Unknown type name 'ファイル名' at position 0 in ファイル名.
Loading history...
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 ファイル名
0 ignored issues
show
Documentation Bug introduced by
The doc comment ファイル名 at position 0 could not be parsed: Unknown type name 'ファイル名' at position 0 in ファイル名.
Loading history...
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 ステータスコード
0 ignored issues
show
Documentation Bug introduced by
The doc comment ステータスコード at position 0 could not be parsed: Unknown type name 'ステータスコード' at position 0 in ステータスコード.
Loading history...
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;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
522
    }
523
524
    /**
525
     * レスポンス送出を開始する
526
     */
527
    public function start()
528
    {
529
        ob_start();
530
        ob_implicit_flush(false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $flag of ob_implicit_flush(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

530
        ob_implicit_flush(/** @scrutinizer ignore-type */ false);
Loading history...
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 表示内容
0 ignored issues
show
Documentation Bug introduced by
The doc comment 表示内容 at position 0 could not be parsed: Unknown type name '表示内容' at position 0 in 表示内容.
Loading history...
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