-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCallableStream.php
166 lines (142 loc) · 3.47 KB
/
CallableStream.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<?php
/*
* This file is part of the Koded package.
*
* (c) Mihail Binev <[email protected]>
*
* Please view the LICENSE distributed with this source code
* for the full copyright and license information.
*
*/
namespace Koded\Http;
use Exception;
use Generator;
use Psr\Http\Message\StreamInterface;
use ReflectionFunction;
use RuntimeException;
use Throwable;
use function fclose;
use function feof;
use function fopen;
use function fread;
use function fseek;
use function fwrite;
use function mb_strlen;
class CallableStream implements StreamInterface
{
private $callable;
private int $position = 0;
private bool $isGenerator;
public function __construct(callable $callable)
{
$this->callable = $callable;
$this->isGenerator = (new ReflectionFunction($this->callable))
->isGenerator();
}
public function __destruct()
{
$this->close();
}
public function __toString(): string
{
try {
return $this->getContents();
} catch (RuntimeException) {
return '';
}
}
public function close(): void
{
$this->detach();
}
public function detach()
{
$this->callable = null;
$this->position = 0;
}
public function getSize(): ?int
{
return null;
}
public function tell(): int
{
return $this->position;
}
public function eof(): bool
{
return null === $this->callable;
}
public function seek($offset, $whence = SEEK_SET): void
{
throw new RuntimeException('Cannot seek in CallableStream');
}
public function rewind(): void
{
throw new RuntimeException('Cannot rewind the CallableStream');
}
public function write($string): int
{
throw new RuntimeException('Cannot write to CallableStream');
}
public function read($length): string
{
$content = '';
if (null === $this->callable) {
return $content;
}
try {
foreach ($this->reader($length) as $chunk) {
$content .= $chunk;
$this->position += mb_strlen($chunk);
}
} catch (Exception $e) {
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
} finally {
$this->callable = null;
}
return $content;
}
public function getContents(): string
{
return $this->read(65536); // 64KB
}
public function getMetadata($key = null): ?array
{
return $key ? null : [];
}
public function isSeekable(): bool
{
return false;
}
public function isWritable(): bool
{
return false;
}
public function isReadable(): bool
{
return true;
}
/**
* @param int $length
*
* @return Generator
* @throws RuntimeException
*/
private function reader(int $length): Generator
{
if ($this->isGenerator) {
yield from ($this->callable)();
} elseif ($resource = fopen('php://temp', 'r+')) {
try {
fwrite($resource, ($this->callable)());
} catch (Throwable $e) {
throw new RuntimeException('Cannot write to stream', 0, $e);
}
fseek($resource, 0);
while (false === feof($resource)) {
yield fread($resource, $length);
}
fclose($resource);
}
}
}