Test Failed
Push — master ( 2889f4...412d23 )
by Kirill
04:40
created

JsonStream::shouldStarts()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
/**
3
 * This file is part of GitterApi package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Gitter\Support;
11
12
use Psr\Http\Message\StreamInterface;
13
use Psr\Log\LoggerInterface;
14
15
/**
16
 * Class JsonStream
17
 * @package Gitter\Support
18
 */
19
class JsonStream
20
{
21
    /**
22
     * @var string[]
23
     */
24
    const JSON_STARTS_WITH = ['{', '['];
25
26
    /**
27
     * @var int
28
     */
29
    const BUFFER_MAX_SIZE = 2 ** 14;
30
31
    /**
32
     * @var string
33
     */
34
    private $bufferSize;
35
36
    /**
37
     * @var string
38
     */
39
    private $endsWith = ']';
40
41
    /**
42
     * @var string
43
     */
44
    private $buffer = '';
45
46
    /**
47
     * @var int
48
     */
49
    private $chunkSize = 1;
50
51
    /**
52
     * @var LoggerInterface
53
     */
54
    private $logger;
55
56
    /**
57
     * JsonStream constructor.
58
     * @param int $bufferSize
59
     */
60
    public function __construct(int $bufferSize = self::BUFFER_MAX_SIZE)
61
    {
62
        $this->bufferSize = $bufferSize;
63
    }
64
65
    /**
66
     * @param LoggerInterface $logger
67
     */
68
    public function setLogger(LoggerInterface $logger)
69
    {
70
        $this->logger = $logger;
71
    }
72
73
    /**
74
     * @throws \OutOfBoundsException
75
     */
76
    private function checkSize()
77
    {
78
        if ($this->bufferSize > 0 && \strlen($this->buffer) > $this->bufferSize) {
79
            throw new \OutOfBoundsException(
80
                sprintf('Memory leak detected. Buffer size out of %s bytes', $this->bufferSize)
81
            );
82
        }
83
    }
84
85
    /**
86
     * @param string $data
87
     * @return bool
88
     */
89
    private function shouldStarts(string $data): bool
90
    {
91
        return $this->buffer === '' && \in_array($data[0], self::JSON_STARTS_WITH, true);;
92
    }
93
94
    /**
95
     * @param string $data
96
     * @param \Closure|null $callback
97
     * @return mixed|null
98
     */
99
    public function push(string $data, \Closure $callback = null)
100
    {
101
        // Buffer are empty and input starts with "[" or "{"
102
        $canBeBuffered = $this->shouldStarts($data);
103
104
        // Data can be starts buffering
105
        if ($canBeBuffered) {
106
            $this->endsWith = $data[0] === '[' ? ']' : '}';
107
        }
108
109
        // Add chunks for non empty buffer
110
        if ($canBeBuffered || $this->buffer !== '') {
111
            $this->buffer .= $data;
112
113
            if ($this->isEnds($data)) {
114
                $object = \json_decode($this->buffer, true);
115
116
                if (\json_last_error() === JSON_ERROR_NONE) {
117
                    $this->buffer = '';
118
119
                    if ($callback !== null) {
120
                        $callback($object);
121
                    }
122
123
                    return $object;
124
                }
125
            }
126
        }
127
128
        return null;
129
    }
130
131
    /**
132
     * @param string $text
133
     * @return bool
134
     */
135
    private function isEnds(string $text): bool
136
    {
137
        $text = \trim($text);
138
139
        return $text && $text[\strlen($text) - 1] === $this->endsWith;
140
    }
141
142
    /**
143
     * @param StreamInterface $stream
144
     * @return \Generator
145
     * @throws \OutOfBoundsException
146
     * @throws \RuntimeException
147
     */
148
    public function stream(StreamInterface $stream): \Generator
149
    {
150
        while (!$stream->eof()) {
151
            $data = $stream->read($this->chunkSize);
152
153
            $output = $this->push($data);
154
            if ($output !== null) {
155
                yield $output;
156
            }
157
158
            $this->checkSize();
159
        }
160
    }
161
}
162