CallableStream   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 134
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 7
Bugs 0 Features 2
Metric Value
eloc 48
dl 0
loc 134
ccs 63
cts 63
cp 1
rs 10
c 7
b 0
f 2
wmc 27

18 Methods

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