StringIteratorSource::read()   B
last analyzed

Complexity

Conditions 7
Paths 10

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 15
c 1
b 0
f 0
nc 10
nop 1
dl 0
loc 25
ccs 16
cts 16
cp 1
crap 7
rs 8.8333
1
<?php
2
declare(strict_types=1);
3
4
namespace JsonDecodeStream\Source;
5
6
use ArrayIterator;
7
use IteratorIterator;
8
use JsonDecodeStream\Exception\SourceException;
9
use Throwable;
10
use Traversable;
11
12
class StringIteratorSource implements SourceInterface
13
{
14
    /** @var IteratorIterator */
15
    protected $stringIterator;
16
    /** @var string|null */
17
    protected $previousPart;
18
    /** @var callable|null */
19
    protected $stringIteratorFactory;
20
    /** @var bool */
21
    protected $isRewoundInitially = false;
22
23
    /**
24
     * StringIteratorSource constructor.
25
     * @param $iterable
26
     * @throws SourceException
27
     */
28 7
    public function __construct($iterable)
29
    {
30 7
        if (is_callable($iterable)) {
31 1
            $this->stringIteratorFactory = $iterable;
32 1
            $iterable = call_user_func($iterable);
33
        }
34 7
        $this->import($iterable);
35 6
    }
36
37
    /**
38
     * @param $iterable array|iterable|Traversable
39
     * @throws SourceException
40
     */
41 8
    protected function import($iterable)
42
    {
43 8
        if (is_array($iterable)) {
44 1
            $iterable = new ArrayIterator($iterable);
45
        }
46
47 8
        if ($iterable instanceof Traversable) {
48 7
            $this->stringIterator = new IteratorIterator($iterable);
49
        } else {
50 1
            throw new SourceException(
51
                'Can not iterate over '
52 1
                . (is_object($iterable) ? get_class($iterable) : gettype($iterable))
53
            );
54
        }
55 7
        $this->isRewoundInitially = false;
56 7
    }
57
58 6
    public function isEof(): bool
59
    {
60 6
        return $this->isRewoundInitially && empty($this->previousPart) && !$this->stringIterator->valid();
61
    }
62
63
    /**
64
     * @param int $bytes
65
     * @return string
66
     * @throws SourceException
67
     */
68 9
    public function read(int $bytes): string
69
    {
70 9
        if (!$this->isRewoundInitially && !$this->stringIterator->valid()) {
71 9
            $this->stringIterator->rewind();
72 9
            $this->isRewoundInitially = true;
73
        }
74
75 9
        $part = $this->previousPart ?? '';
76 9
        $this->previousPart = null;
77
78 9
        while (strlen($part) < $bytes && $this->stringIterator->valid()) {
79 9
            $nextPart = $this->stringIterator->current();
80 9
            if (!is_string($nextPart)) {
81 1
                throw new SourceException('Iterator provided a non-string value');
82
            }
83 9
            $part .= $nextPart;
84 9
            $this->stringIterator->next();
85
        }
86
87 9
        if (strlen($part) > $bytes) {
88 2
            $this->previousPart = substr($part, $bytes);
89 2
            $part = substr($part, 0, $bytes);
90
        }
91
92 9
        return $part;
93
    }
94
95
    /**
96
     * @throws SourceException
97
     */
98 7
    public function rewind(): void
99
    {
100 7
        if (!$this->isRewoundInitially) {
101 6
            return;
102
        }
103
104
        try {
105 2
            if ($this->stringIteratorFactory) {
106 1
                $recreatedIterator = call_user_func($this->stringIteratorFactory);
107 1
                $this->import($recreatedIterator);
108
            } else {
109 2
                $this->stringIterator->rewind();
110
            }
111 1
        } catch (Throwable $exception) {
112 1
            throw new SourceException('This iterator is not rewindable', 0, $exception);
113
        }
114 1
    }
115
}
116