HTTPStreamResponse::getBody()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 2
nop 0
dl 0
loc 19
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
use BadMethodCallException;
6
7
/**
8
 * A response which contains a streamable data source.
9
 *
10
 * @package framework
11
 * @subpackage control
12
 */
13
class HTTPStreamResponse extends HTTPResponse
14
{
15
16
    /**
17
     * Stream source for this response
18
     *
19
     * @var resource
20
     */
21
    protected $stream = null;
22
23
    /**
24
     * Set to true if this stream has been consumed.
25
     * A consumed non-seekable stream will not be re-consumable
26
     *
27
     * @var bool
28
     */
29
    protected $consumed = false;
30
31
    /**
32
     * HTTPStreamResponse constructor.
33
     * @param resource $stream Data stream
34
     * @param int $contentLength size of the stream in bytes
35
     * @param int $statusCode The numeric status code - 200, 404, etc
36
     * @param string $statusDescription The text to be given alongside the status code.
37
     */
38
    public function __construct($stream, $contentLength, $statusCode = null, $statusDescription = null)
39
    {
40
        parent::__construct(null, $statusCode, $statusDescription);
41
        $this->setStream($stream);
42
        if ($contentLength) {
43
            $this->addHeader('Content-Length', $contentLength);
44
        }
45
    }
46
47
    /**
48
     * Determine if a stream is seekable
49
     *
50
     * @return bool
51
     */
52
    protected function isSeekable()
53
    {
54
        $stream = $this->getStream();
55
        if (!$stream) {
0 ignored issues
show
introduced by
$stream is of type resource, thus it always evaluated to false.
Loading history...
56
            return false;
57
        }
58
        $metadata = stream_get_meta_data($stream);
59
        return $metadata['seekable'];
60
    }
61
62
    /**
63
     * @return resource
64
     */
65
    public function getStream()
66
    {
67
        return $this->stream;
68
    }
69
70
    /**
71
     * @param resource $stream
72
     * @return $this
73
     */
74
    public function setStream($stream)
75
    {
76
        $this->setBody(null);
77
        $this->stream = $stream;
78
        return $this;
79
    }
80
81
    /**
82
     * Get body prior to stream traversal
83
     *
84
     * @return string
85
     */
86
    public function getSavedBody()
87
    {
88
        return parent::getBody();
89
    }
90
91
    public function getBody()
92
    {
93
        $body = $this->getSavedBody();
94
        if (isset($body)) {
95
            return $body;
96
        }
97
98
        // Consume stream into string
99
        $body = $this->consumeStream(function ($stream) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $body is correct as $this->consumeStream(function(...) { /* ... */ }) targeting SilverStripe\Control\HTT...sponse::consumeStream() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
100
            $body = stream_get_contents($stream);
101
102
            // If this stream isn't seekable, we'll need to save the body
103
            // in case of subsequent requests.
104
            if (!$this->isSeekable()) {
105
                $this->setBody($body);
106
            }
107
            return $body;
108
        });
109
        return $body;
110
    }
111
112
    /**
113
     * Safely consume the stream
114
     *
115
     * @param callable $callback Callback which will perform the consumable action on the stream
116
     * @return mixed Result of $callback($stream) or null if no stream available
117
     * @throws BadMethodCallException Throws exception if stream can't be re-consumed
118
     */
119
    protected function consumeStream($callback)
120
    {
121
        // Load from stream
122
        $stream = $this->getStream();
123
        if (!$stream) {
0 ignored issues
show
introduced by
$stream is of type resource, thus it always evaluated to false.
Loading history...
124
            return null;
125
        }
126
127
        // Check if stream must be rewound
128
        if ($this->consumed) {
129
            if (!$this->isSeekable()) {
130
                throw new BadMethodCallException(
131
                    "Unseekable stream has already been consumed"
132
                );
133
            }
134
            rewind($stream);
135
        }
136
137
        // Consume
138
        $this->consumed = true;
139
        return $callback($stream);
140
    }
141
142
    /**
143
     * Output body of this response to the browser
144
     */
145
    protected function outputBody()
146
    {
147
        // If the output has been overwritten, or the stream is irreversable and has
148
        // already been consumed, return the cached body.
149
        $body = $this->getSavedBody();
150
        if ($body) {
151
            echo $body;
152
            return;
153
        }
154
155
        // Stream to output
156
        if ($this->getStream()) {
157
            $this->consumeStream(function ($stream) {
158
                fpassthru($stream);
159
            });
160
            return;
161
        }
162
163
        // Fail over
164
        parent::outputBody();
165
    }
166
}
167