EventStreamFeedIterator   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7
Metric Value
wmc 21
lcom 1
cbo 7
dl 0
loc 179
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A readEventStreamFeed() 0 8 1
A current() 0 12 3
A next() 0 8 2
A readForward() 0 9 3
A readBackward() 0 9 2
A key() 0 4 1
A valid() 0 4 1
A rewind() 0 13 2
A getStartUri() 0 17 3
A getReadDirection() 0 4 1
1
<?php
2
3
namespace RayRutjes\GetEventStore\Client\Http\Feed;
4
5
use RayRutjes\GetEventStore\Client\Http\HttpClient;
6
use RayRutjes\GetEventStore\Client\Http\ReadEventStreamFeedRequestFactory;
7
use RayRutjes\GetEventStore\Client\Http\ReadEventStreamFeedResponseInspector;
8
use RayRutjes\GetEventStore\ReadDirection;
9
use RayRutjes\GetEventStore\StreamId;
10
11
class EventStreamFeedIterator implements \Iterator
12
{
13
    /**
14
     * @var StreamId
15
     */
16
    private $streamId;
17
18
    /**
19
     * @var HttpClient
20
     */
21
    private $client;
22
23
    /**
24
     * @var EventStreamFeed
25
     */
26
    private $currentFeed;
27
28
    /**
29
     * @var string
30
     */
31
    private $headUri;
32
33
    /**
34
     * @var string
35
     */
36
    private $startUri;
37
38
    /**
39
     * @var ReadDirection
40
     */
41
    private $readDirection;
42
43
    /**
44
     * @var bool
45
     */
46
    private $initialized = false;
47
48
    /**
49
     * @var int
50
     */
51
    private $currentKey = 0;
52
53
    /**
54
     * You can either iterate reading from the start of the stream, beginning at the first event registered and move forward,
55
     * or you can iterate from the end, meaning that the first iteration will correspond to the latest event registered,
56
     * and move backward to the beginning of the stream.
57
     *
58
     * @param StreamId   $streamId
59
     * @param HttpClient $client
60
     * @param bool       $readFromStart
61
     */
62
    public function __construct(StreamId $streamId, HttpClient $client, $readFromStart = true)
63
    {
64
        $this->streamId = $streamId;
65
        $this->client = $client;
66
        $this->headUri = sprintf('streams/%s', $streamId->toString());
67
68
        $direction = $readFromStart ? ReadDirection::FORWARD : ReadDirection::BACKWARD;
69
        $this->readDirection = new ReadDirection($direction);
70
    }
71
72
    /**
73
     * @param string $uri
74
     *
75
     * @return EventStreamFeed
76
     */
77
    private function readEventStreamFeed(string $uri): EventStreamFeed
78
    {
79
        $factory = new ReadEventStreamFeedRequestFactory($uri);
80
        $inspector = new ReadEventStreamFeedResponseInspector();
81
        $this->client->send($factory->buildRequest(), $inspector);
82
83
        return $inspector->getFeed();
84
    }
85
86
    /**
87
     * @return EventStreamFeed
88
     */
89
    public function current(): EventStreamFeed
90
    {
91
        if (null === $this->currentFeed) {
92
            if (true === $this->initialized) {
93
                throw new \OutOfBoundsException('Stream overflow.');
94
            } else {
95
                $this->rewind();
96
            }
97
        }
98
99
        return $this->currentFeed;
100
    }
101
102
    public function next()
103
    {
104
        $this->currentKey++;
105
        if ($this->readDirection->isForward()) {
106
            return $this->readForward();
107
        }
108
        $this->readBackward();
109
    }
110
111
    private function readForward()
112
    {
113
        if ($this->currentFeed->hasPreviousLink() && !$this->currentFeed->isHeadOfStream()) {
114
            $this->currentFeed = $this->readEventStreamFeed($this->currentFeed->getPreviousLink()->getUri());
115
116
            return;
117
        }
118
        $this->currentFeed = null;
119
    }
120
121
    private function readBackward()
122
    {
123
        if ($this->currentFeed->hasNextLink()) {
124
            $this->currentFeed = $this->readEventStreamFeed($this->currentFeed->getNextLink()->getUri());
125
126
            return;
127
        }
128
        $this->currentFeed = null;
129
    }
130
131
    /**
132
     * @return int
133
     */
134
    public function key(): int
135
    {
136
        return $this->currentKey;
137
    }
138
139
    /**
140
     * @return bool
141
     */
142
    public function valid(): bool
143
    {
144
        return null !== $this->currentFeed;
145
    }
146
147
    public function rewind()
148
    {
149
        $this->currentKey = 0;
150
        if ($this->readDirection->isForward()) {
151
            $this->startUri = $this->getStartUri();
152
            $this->currentFeed = $this->readEventStreamFeed($this->startUri);
153
        } else {
154
            $this->currentFeed = $this->readEventStreamFeed($this->headUri);
155
        }
156
        $this->initialized = true;
157
        // Todo: not sure how we should handle errors here.
158
        // Todo: for now, let it bubble.
159
    }
160
161
    /**
162
     * @return string
163
     */
164
    private function getStartUri(): string
165
    {
166
        if (null !== $this->startUri) {
167
            return $this->startUri;
168
        }
169
170
        // It is recommended to not guess the uris for backward compatibility.
171
        // So we have to make an intermediary request to get the start uri.
172
        $feed = $this->readEventStreamFeed($this->headUri);
173
        if ($feed->hasLastLink()) {
174
            return $feed->getLastLink()->getUri();
175
        }
176
177
        // If we came so far it means we are dealing with a feed of exactly one page.
178
        // So head = start.
179
        return $this->headUri;
180
    }
181
182
    /**
183
     * @return ReadDirection
184
     */
185
    public function getReadDirection()
186
    {
187
        return $this->readDirection;
188
    }
189
}
190