Passed
Push — master ( 9ffd86...045e57 )
by Anton
04:30
created

SapiEmitter::assertNoPreviousOutput()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 4
nc 3
nop 0
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
/**
9
 * @see       https://github.com/zendframework/zend-httphandlerrunner for the canonical source repository
10
 * @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
11
 * @license   https://github.com/zendframework/zend-httphandlerrunner/blob/master/LICENSE.md New BSD License
12
 */
13
14
declare(strict_types=1);
15
16
namespace Spiral\Http\Emitter;
17
18
use Psr\Http\Message\ResponseInterface;
19
use Spiral\Http\EmitterInterface;
20
use Spiral\Http\Exception\EmitterException;
21
22
/**
23
 * Source code has been extracted from Zend/Diactoros.
24
 */
25
final class SapiEmitter implements EmitterInterface
26
{
27
    /**
28
     * Emits a response for a PHP SAPI environment.
29
     *
30
     * Emits the status line and headers via the header() function, and the
31
     * body content via the output buffer.
32
     *
33
     * @param ResponseInterface $response
34
     * @return bool
35
     */
36
    public function emit(ResponseInterface $response): bool
37
    {
38
        $this->assertNoPreviousOutput();
39
40
        $this->emitHeaders($response);
41
        $this->emitStatusLine($response);
42
        $this->emitBody($response);
43
44
        return true;
45
    }
46
47
    /**
48
     * Emit the message body.
49
     *
50
     * @param ResponseInterface $response
51
     */
52
    private function emitBody(ResponseInterface $response): void
53
    {
54
        echo $response->getBody();
55
    }
56
57
    /**
58
     * Checks to see if content has previously been sent.
59
     *
60
     * If either headers have been sent or the output buffer contains content,
61
     * raises an exception.
62
     *
63
     * @throws EmitterException if headers have already been sent.
64
     * @throws EmitterException if output is present in the output buffer.
65
     */
66
    private function assertNoPreviousOutput()
67
    {
68
        if (headers_sent()) {
69
            throw new EmitterException("Unable to emit response, headers already send.");
70
        }
71
72
        if (ob_get_level() > 0 && ob_get_length() > 0) {
73
            throw new EmitterException("Unable to emit response, found non closed buffered output.");
74
        }
75
    }
76
77
    /**
78
     * Emit the status line.
79
     *
80
     * Emits the status line using the protocol version and status code from
81
     * the response; if a reason phrase is available, it, too, is emitted.
82
     *
83
     * It is important to mention that this method should be called after
84
     * `emitHeaders()` in order to prevent PHP from changing the status code of
85
     * the emitted response.
86
     *
87
     * @param ResponseInterface $response
88
     */
89
    private function emitStatusLine(ResponseInterface $response): void
90
    {
91
        $reasonPhrase = $response->getReasonPhrase();
92
        $statusCode = $response->getStatusCode();
93
94
        header(sprintf(
95
            'HTTP/%s %d%s',
96
            $response->getProtocolVersion(),
97
            $statusCode,
98
            ($reasonPhrase ? ' ' . $reasonPhrase : '')
99
        ), true, $statusCode);
100
    }
101
102
    /**
103
     * Emit response headers.
104
     *
105
     * Loops through each header, emitting each; if the header value
106
     * is an array with multiple values, ensures that each is sent
107
     * in such a way as to create aggregate headers (instead of replace
108
     * the previous).
109
     *
110
     * @param ResponseInterface $response
111
     */
112
    private function emitHeaders(ResponseInterface $response): void
113
    {
114
        $statusCode = $response->getStatusCode();
115
116
        foreach ($response->getHeaders() as $header => $values) {
117
            $name = $this->filterHeader($header);
118
            $first = $name === 'Set-Cookie' ? false : true;
119
            foreach ($values as $value) {
120
                header(sprintf(
121
                    '%s: %s',
122
                    $name,
123
                    $value
124
                ), $first, $statusCode);
125
                $first = false;
126
            }
127
        }
128
    }
129
130
    /**
131
     * Filter a header name to wordcase
132
     *
133
     * @param string $header
134
     * @return string
135
     */
136
    private function filterHeader(string $header): string
137
    {
138
        return ucwords($header, '-');
139
    }
140
}