Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
StreamResponse
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
2 / 2
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
11
 render
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Miniframe\Response;
4
5use Miniframe\Core\Response;
6
7class StreamResponse extends Response
8{
9    /**
10     * Resource of a stream.
11     *
12     * @var resource
13     */
14    private $resource;
15
16    /**
17     * Disposition options
18     */
19    public const
20        DISPOSITION_ATTACHMENT = 1,
21        DISPOSITION_INLINE = 2
22    ;
23
24    /**
25     * Streams a resource back to the client
26     *
27     * @param resource    $resource    Resource of a stream (`$resource = fopen('/path/to/large-file.zip', 'r');`).
28     * @param string|null $filename    Name of the file.
29     * @param string|null $contentType Type of the file.
30     * @param integer     $disposition Type of the stream (self::DISPOSITION_ATTACHMENT or self::DISPOSITION_INLINE).
31     */
32    public function __construct(
33        $resource,
34        string $filename = null,
35        string $contentType = null,
36        int $disposition = self::DISPOSITION_ATTACHMENT
37    ) {
38        if (is_resource($resource) === false) {
39            throw new \InvalidArgumentException(
40                'Argument must be a valid resource type. ' . gettype($resource) . ' given.'
41            );
42        }
43        $this->resource = $resource;
44        switch ($disposition) {
45            case self::DISPOSITION_ATTACHMENT:
46                $dispositionStr = 'attachment';
47                break;
48            case self::DISPOSITION_INLINE:
49                $dispositionStr = 'inline';
50                break;
51            default:
52                throw new \InvalidArgumentException(
53                    'Argument must be a valid disposition. ' . var_export($disposition, true) . ' given.'
54                );
55        }
56
57        // Add Content Type header, when specified
58        if ($contentType !== null) {
59            $this->addHeader('Content-type: ' . $contentType);
60        }
61
62        // Add Content-length and Last-Modified, when known
63        $stat = fstat($resource);
64        if (isset($stat['size']) && $stat['size'] > 0) {
65            $this->addHeader('Content-length: ' . $stat['size']);
66        }
67        if (isset($stat['mtime']) && $stat['mtime'] > 0) {
68            $this->addHeader('Last-Modified: ' . date('r', $stat['mtime']));
69        }
70
71        // Transfer is binary
72        $this->addHeader('Content-Transfer-Encoding: Binary');
73
74        // Compile Content-disposition header
75        $contentDisposition = 'Content-disposition: ' . $dispositionStr;
76        if ($filename !== null) {
77            $contentDisposition .= '; filename="' . rawurlencode($filename) . '"';
78        }
79        $this->addHeader($contentDisposition);
80    }
81
82    /**
83     * Passes thru the stream data to STDOUT. Always returns an empty string.
84     *
85     * @return string
86     */
87    public function render(): string
88    {
89        $currentLimit = ini_get('max_execution_time');
90        set_time_limit(0);
91        fpassthru($this->resource);
92        set_time_limit((int)$currentLimit);
93        fclose($this->resource);
94        return '';
95    }
96}