Completed
Push — master ( 45ec31...d70347 )
by Ivan
03:16
created

Emitter::emitBodyRange()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 0
cts 23
cp 0
rs 8.4746
c 0
b 0
f 0
cc 7
nc 10
nop 3
crap 56
1
<?php
2
3
namespace vakata\http;
4
5
use RuntimeException;
6
7
use function ob_get_length;
8
use function ob_get_level;
9
use function sprintf;
10
use function str_replace;
11
use function ucwords;
12
13
class Emitter
14
{
15
    /**
16
     * Checks to see if content has previously been sent.
17
     *
18
     * If either headers have been sent or the output buffer contains content,
19
     * raises an exception.
20
     *
21
     * @throws RuntimeException if headers have already been sent.
22
     * @throws RuntimeException if output is present in the output buffer.
23
     */
24
    private function assertNoPreviousOutput()
25
    {
26
        if (headers_sent()) {
27
            throw new RuntimeException('Unable to emit response; headers already sent');
28
        }
29
30
        if (ob_get_level() > 0 && ob_get_length() > 0) {
31
            throw new RuntimeException('Output has been emitted previously; cannot emit response');
32
        }
33
    }
34
35
    /**
36
     * Emit the status line.
37
     *
38
     * Emits the status line using the protocol version and status code from
39
     * the response; if a reason phrase is available, it, too, is emitted.
40
     *
41
     * It is important to mention that this method should be called after
42
     * `emitHeaders()` in order to prevent PHP from changing the status code of
43
     * the emitted response.
44
     *
45
     * @param Response $response
46
     *
47
     * @see \Zend\Diactoros\Response\SapiEmitterTrait::emitHeaders()
48
     */
49
    private function emitStatusLine(Response $response)
50
    {
51
        $reasonPhrase = $response->getReasonPhrase();
52
        $statusCode   = $response->getStatusCode();
53
54
        header(sprintf(
55
            'HTTP/%s %d%s',
56
            $response->getProtocolVersion(),
57
            $statusCode,
58
            ($reasonPhrase ? ' ' . $reasonPhrase : '')
59
        ), true, $statusCode);
60
    }
61
62
    /**
63
     * Emit response headers.
64
     *
65
     * Loops through each header, emitting each; if the header value
66
     * is an array with multiple values, ensures that each is sent
67
     * in such a way as to create aggregate headers (instead of replace
68
     * the previous).
69
     *
70
     * @param Response $response
71
     */
72
    private function emitHeaders(Response $response)
73
    {
74
        $statusCode = $response->getStatusCode();
75
76
        foreach ($response->getHeaders() as $header => $values) {
77
            $name  = $this->filterHeader($header);
78
            $first = $name === 'Set-Cookie' ? false : true;
79
            foreach ($values as $value) {
80
                header(sprintf(
81
                    '%s: %s',
82
                    $name,
83
                    $value
84
                ), $first, $statusCode);
85
                $first = false;
86
            }
87
        }
88
    }
89
90
    /**
91
     * Filter a header name to wordcase
92
     *
93
     * @param string $header
94
     * @return string
95
     */
96
    private function filterHeader($header)
97
    {
98
        $filtered = str_replace('-', ' ', $header);
99
        $filtered = ucwords($filtered);
100
        return str_replace(' ', '-', $filtered);
101
    }
102
103
    /**
104
     * Emits a response for a PHP SAPI environment.
105
     *
106
     * Emits the status line and headers via the header() function, and the
107
     * body content via the output buffer.
108
     *
109
     * @param Response $response
110
     */
111
    public function emit(Response $response, $maxBufferLength = 8192)
112
    {
113
        $this->assertNoPreviousOutput();
114
115
        $this->emitHeaders($response);
116
        $this->emitStatusLine($response);
117
        
118
        if ($response->hasCallback()) {
119
            call_user_func_array($response->getCallback());
120
        } elseif ($response->hasHeader('Accept-Ranges')) {
121
            $range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
122
            if (is_array($range) && $range[0] === 'bytes') {
123
                $this->emitBodyRange($range, $response, $maxBufferLength);
124
            } else {
125
                $body = $response->getBody();
126
                if ($body->isSeekable()) {
127
                    $body->rewind();
128
                }
129
                if (!$body->isReadable()) {
130
                    echo $body;
131
                } else {
132
                    while (!$body->eof()) {
133
                        echo $body->read($maxBufferLength);
134
                    }
135
                }
136
            }
137
        } else {
138
            echo $response->getBody();
139
        }
140
    }
141
142
    /**
143
     * Parse content-range header
144
     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16
145
     *
146
     * @param string $header
147
     * @return false|array [unit, first, last, length]; returns false if no
148
     *     content range or an invalid content range is provided
149
     */
150
    private function parseContentRange($header)
151
    {
152
        if (preg_match('/(?P<unit>[\w]+)\s+(?P<first>\d+)-(?P<last>\d+)\/(?P<length>\d+|\*)/', $header, $matches)) {
153
            return [
154
                $matches['unit'],
155
                (int) $matches['first'],
156
                (int) $matches['last'],
157
                $matches['length'] === '*' ? '*' : (int) $matches['length'],
158
            ];
159
        }
160
        return false;
161
    }
162
    /**
163
     * Emit a range of the message body.
164
     *
165
     * @param array $range
166
     * @param ResponseInterface $response
167
     * @param int $maxBufferLength
168
     */
169
    private function emitBodyRange(array $range, Response $response, $maxBufferLength)
170
    {
171
        list($unit, $first, $last, $length) = $range;
0 ignored issues
show
Unused Code introduced by
The assignment to $unit is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $length is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
172
173
        $body = $response->getBody();
174
175
        $length = $last - $first + 1;
176
177
        if ($body->isSeekable()) {
178
            $body->seek($first);
179
180
            $first = 0;
181
        }
182
183
        if (! $body->isReadable()) {
184
            echo substr($body->getContents(), $first, $length);
185
            return;
186
        }
187
188
        $remaining = $length;
189
190
        while ($remaining >= $maxBufferLength && ! $body->eof()) {
191
            $contents   = $body->read($maxBufferLength);
192
            $remaining -= strlen($contents);
193
194
            echo $contents;
195
        }
196
197
        if ($remaining > 0 && ! $body->eof()) {
198
            echo $body->read($remaining);
199
        }
200
    }
201
}
202