Completed
Push — batch-iterator ( dd6368 )
by Davide
02:02
created

EventStore::deleteStream()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 4
Bugs 0 Features 2
Metric Value
c 4
b 0
f 2
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
crap 2
1
<?php
2
3
namespace EventStore;
4
5
use EventStore\Exception\ConnectionFailedException;
6
use EventStore\Exception\StreamDeletedException;
7
use EventStore\Exception\StreamNotFoundException;
8
use EventStore\Exception\UnauthorizedException;
9
use EventStore\Exception\WrongExpectedVersionException;
10
use EventStore\Http\Exception\ClientException;
11
use EventStore\Http\Exception\RequestException;
12
use EventStore\Http\GuzzleHttpClient;
13
use EventStore\Http\HttpClientInterface;
14
use EventStore\Http\ResponseCode;
15
use EventStore\StreamFeed\EntryEmbedMode;
16
use EventStore\StreamFeed\Event;
17
use EventStore\StreamFeed\LinkRelation;
18
use EventStore\StreamFeed\StreamFeed;
19
use EventStore\StreamFeed\StreamFeedIterator;
20
use EventStore\StreamFeed\StreamUrl;
21
use GuzzleHttp\Psr7\Request;
22
use GuzzleHttp\Psr7\Uri;
23
use Psr\Http\Message\RequestInterface;
24
use Psr\Http\Message\ResponseInterface;
25
26
/**
27
 * Class EventStore
28
 * @package EventStore
29
 */
30
final class EventStore implements EventStoreInterface
31
{
32
    /**
33
     * @var string
34
     */
35
    private $url;
36
37
    /**
38
     * @var Client
39
     */
40
    private $httpClient;
41
42
    /**
43
     * @var ResponseInterface
44
     */
45
    private $lastResponse;
46
47
    /**
48
     * @var array
49
     */
50
    private $badCodeHandlers = [];
51
52
    /**
53
     * @param string $url Endpoint of the EventStore HTTP API
54
     * @param HttpClientInterface the http client
55
     */
56 26
    public function __construct($url, HttpClientInterface $httpClient = null)
57
    {
58 26
        $this->url = $url;
59
60 26
        $this->httpClient = $httpClient ?: new GuzzleHttpClient();
0 ignored issues
show
Documentation Bug introduced by
It seems like $httpClient ?: new \Even...Http\GuzzleHttpClient() of type object<EventStore\Http\HttpClientInterface> is incompatible with the declared type object<EventStore\Client> of property $httpClient.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
61 26
        $this->checkConnection();
62 26
        $this->initBadCodeHandlers();
63 26
    }
64
65
    /**
66
     * Delete a stream
67
     *
68
     * @param string         $streamName Name of the stream
69
     * @param StreamDeletion $mode       Deletion mode (soft or hard)
70
     */
71 4
    public function deleteStream($streamName, StreamDeletion $mode)
72
    {
73 4
        $request = new Request('DELETE', $this->getStreamUrl($streamName));
74
75 4
        if ($mode == StreamDeletion::HARD) {
76 3
            $request = $request->withHeader('ES-HardDelete', 'true');
77 3
        }
78
79 4
        $this->sendRequest($request);
80 4
    }
81
82
    /**
83
     * Get the response from the last HTTP call to the EventStore API
84
     *
85
     * @return ResponseInterface
86
     */
87 23
    public function getLastResponse()
88 1
    {
89 23
        return $this->lastResponse;
90
    }
91
92
    /**
93
     * Navigate stream feed through link relations
94
     *
95
     * @param  StreamFeed      $streamFeed The stream feed to navigate through
96
     * @param  LinkRelation    $relation   The "direction" expressed as link relation
97
     * @return null|StreamFeed
98
     */
99 8
    public function navigateStreamFeed(StreamFeed $streamFeed, LinkRelation $relation)
100
    {
101 8
        $url = $streamFeed->getLinkUrl($relation);
102
103 8
        if (empty($url)) {
104 3
            return null;
105
        }
106
107 7
        return $this->readStreamFeed($url, $streamFeed->getEntryEmbedMode());
108
    }
109
110
    /**
111
     * Open a stream feed for read and navigation
112
     *
113
     * @param  string         $streamName The stream name
114
     * @param  EntryEmbedMode $embedMode  The event entries embed mode (none, rich or body)
115
     * @return StreamFeed
116
     */
117 18
    public function openStreamFeed($streamName, EntryEmbedMode $embedMode = null)
118
    {
119 18
        $url = $this->getStreamUrl($streamName);
120
121 18
        return $this->readStreamFeed($url, $embedMode);
122
    }
123
124
    /**
125
     * Read a single event
126
     *
127
     * @param  string $eventUrl The url of the event
128
     * @return Event
129
     */
130 3
    public function readEvent($eventUrl)
131
    {
132 3
        $request = $this->getJsonRequest($eventUrl);
133 3
        $this->sendRequest($request);
134
135 3
        $this->ensureStatusCodeIsGood($eventUrl);
136
137 2
        $jsonResponse = $this->lastResponseAsJson();
138
139 2
        return $this->createEventFromResponseContent($jsonResponse['content']);
140
    }
141
142
    /**
143
     * Read a single event
144
     *
145
     * @param  string $eventUrl The url of the event
0 ignored issues
show
Documentation introduced by
There is no parameter named $eventUrl. Did you maybe mean $eventUrls?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
146
     * @return Event
147
     */
148 7
    public function readEventBatch(array $eventUrls)
149
    {
150 7
        $requests = array_map(
151
            function ($eventUrl) {
152 7
                return $this->getJsonRequest($eventUrl);
153 7
            },
154
            $eventUrls
155 7
        );
156
157 7
        $responses = $this->httpClient->sendRequestBatch($requests);
158
159 7
        return array_map(
160
            function ($response) {
161 7
                return $this
162 7
                    ->createEventFromResponseContent(
163 7
                        json_decode($response->getBody(), true)['content']
164 7
                    )
165 7
                ;
166 7
            },
167
            $responses
168 7
        );
169
    }
170
171
    /**
172
     * @param  array $content
173
     * @return Event
174
     */
175 9
    protected function createEventFromResponseContent(array $content)
176
    {
177 9
        $type = $content['eventType'];
178 9
        $version = (integer) $content['eventNumber'];
179 9
        $data = $content['data'];
180 9
        $metadata = (!empty($content['metadata'])) ? $content['metadata'] : null;
181
182 9
        return new Event($type, $version, $data, $metadata);
183
    }
184
185
    /**
186
     * Write one or more events to a stream
187
     *
188
     * @param  string                                  $streamName      The stream name
189
     * @param  WritableToStream                        $events          Single event or a collection of events
190
     * @param  int                                     $expectedVersion The expected version of the stream
191
     * @throws Exception\WrongExpectedVersionException
192
     */
193 22
    public function writeToStream($streamName, WritableToStream $events, $expectedVersion = ExpectedVersion::ANY)
194
    {
195 22
        if ($events instanceof WritableEvent) {
196 5
            $events = new WritableEventCollection([$events]);
197 5
        }
198
199 22
        $request = new Request(
200 22
            'POST',
201 22
            $this->getStreamUrl($streamName),
202
            [
203 22
                'ES-ExpectedVersion' => intval($expectedVersion),
204 22
                'Content-Type' => 'application/vnd.eventstore.events+json',
205 22
            ],
206 22
            json_encode($events->toStreamData())
207 22
        );
208
209 22
        $this->sendRequest($request);
210
211 22
        $responseStatusCode = $this->getLastResponse()->getStatusCode();
212
213 22
        if (ResponseCode::HTTP_BAD_REQUEST == $responseStatusCode) {
214 1
            throw new WrongExpectedVersionException();
215
        }
216 22
    }
217
218
    /**
219
     * @param  string             $streamName
220
     * @return StreamFeedIterator
221
     */
222 1
    public function forwardStreamFeedIterator($streamName)
223
    {
224 1
        return StreamFeedIterator::forward($this, $streamName);
225
    }
226
227
    /**
228
     * @param  string             $streamName
229
     * @return StreamFeedIterator
230
     */
231 1
    public function backwardStreamFeedIterator($streamName)
232
    {
233 1
        return StreamFeedIterator::backward($this, $streamName);
234
    }
235
236
    /**
237
     * @throws Exception\ConnectionFailedException
238
     */
239 26
    private function checkConnection()
240
    {
241
        try {
242 26
            $request = new Request('GET', $this->url);
243 26
            $this->sendRequest($request);
244 26
        } catch (RequestException $e) {
245 1
            throw new ConnectionFailedException($e->getMessage());
246
        }
247 26
    }
248
249
    /**
250
     * @param  string $streamName
251
     * @return string
252
     */
253 24
    private function getStreamUrl($streamName)
254
    {
255 24
        return (string) StreamUrl::fromBaseUrlAndName($this->url, $streamName);
256
    }
257
258
    /**
259
     * @param  string                            $streamUrl
260
     * @param  EntryEmbedMode                    $embedMode
261
     * @return StreamFeed
262
     * @throws Exception\StreamDeletedException
263
     * @throws Exception\StreamNotFoundException
264
     */
265 18
    private function readStreamFeed($streamUrl, EntryEmbedMode $embedMode = null)
266
    {
267 18
        $request = $this->getJsonRequest($streamUrl);
268
269 18
        if ($embedMode != null && $embedMode != EntryEmbedMode::NONE()) {
270 1
            $uri = Uri::withQueryValue(
271 1
                $request->getUri(),
0 ignored issues
show
Bug introduced by
It seems like $request->getUri() can be null; however, withQueryValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
272 1
                'embed',
273 1
                $embedMode->toNative()
274 1
            );
275
276 1
            $request = $request->withUri($uri);
277 1
        }
278
279 18
        $this->sendRequest($request);
280
281 18
        $this->ensureStatusCodeIsGood($streamUrl);
282
283 15
        return new StreamFeed($this->lastResponseAsJson(), $embedMode);
284
    }
285
286
    /**
287
     * @param  string                                       $uri
288
     * @return \GuzzleHttp\Message\Request|RequestInterface
289
     */
290 18
    private function getJsonRequest($uri)
291
    {
292 18
        return new Request(
293 18
            'GET',
294 18
            $uri,
295
            [
296
                'Accept' => 'application/vnd.eventstore.atom+json'
297 18
            ]
298 18
        );
299
    }
300
301
    /**
302
     * @param RequestInterface $request
303
     */
304 26
    private function sendRequest(RequestInterface $request)
305
    {
306
        try {
307 26
            $this->lastResponse = $this->httpClient->send($request);
308 26
        } catch (ClientException $e) {
309
            $this->lastResponse = $e->getResponse();
310
        }
311 26
    }
312
313
    /**
314
     * @param  string                            $streamUrl
315
     * @throws Exception\StreamDeletedException
316
     * @throws Exception\StreamNotFoundException
317
     * @throws Exception\UnauthorizedException
318
     */
319 18
    private function ensureStatusCodeIsGood($streamUrl)
320
    {
321 18
        $code = $this->lastResponse->getStatusCode();
322
323 18
        if (array_key_exists($code, $this->badCodeHandlers)) {
324 4
            $this->badCodeHandlers[$code]($streamUrl);
325
        }
326 15
    }
327
328 26
    private function initBadCodeHandlers()
329
    {
330 26
        $this->badCodeHandlers = [
331
            ResponseCode::HTTP_NOT_FOUND => function ($streamUrl) {
332 1
                    throw new StreamNotFoundException(
333 1
                        sprintf(
334 1
                            'No stream found at %s',
335
                            $streamUrl
336 1
                        )
337 1
                    );
338 26
                },
339
340
            ResponseCode::HTTP_GONE => function ($streamUrl) {
341 2
                    throw new StreamDeletedException(
342 2
                        sprintf(
343 2
                            'Stream at %s has been permanently deleted',
344
                            $streamUrl
345 2
                        )
346 2
                    );
347 26
                },
348
349 1
            ResponseCode::HTTP_UNAUTHORIZED => function ($streamUrl) {
350 1
                    throw new UnauthorizedException(
351 1
                        sprintf(
352 1
                            'Tried to open stream %s got 401',
353
                            $streamUrl
354 1
                        )
355 1
                    );
356
                }
357 26
        ];
358 26
    }
359
360 15
    private function lastResponseAsJson()
361
    {
362 15
        return json_decode($this->lastResponse->getBody(), true);
363
    }
364
}
365