Passed
Pull Request — master (#7)
by Sandro
02:16
created

Statement::key()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * Sandro Keil (https://sandro-keil.de)
4
 *
5
 * @link      http://github.com/sandrokeil/arangodb-php-client for the canonical source repository
6
 * @copyright Copyright (c) 2018-2019 Sandro Keil
7
 * @license   http://github.com/sandrokeil/arangodb-php-client/blob/master/LICENSE.md New BSD License
8
 */
9
10
namespace ArangoDb;
11
12
use ArangoDb\Exception\ServerException;
13
use ArangoDb\Http\JsonStream;
14
use ArangoDb\Util\Json;
15
use Countable;
16
use Fig\Http\Message\RequestMethodInterface;
17
use Fig\Http\Message\StatusCodeInterface;
18
use Iterator;
19
use Psr\Http\Client\ClientExceptionInterface;
20
use Psr\Http\Client\ClientInterface;
21
use Psr\Http\Message\RequestFactoryInterface;
22
use Psr\Http\Message\RequestInterface;
23
24
final class Statement implements Iterator, Countable
25
{
26
    /**
27
     * "objectType" option entry.
28
     */
29
    public const ENTRY_TYPE = 'objectType';
30
31
    public const ENTRY_TYPE_JSON = 'json';
32
    public const ENTRY_TYPE_ARRAY = 'array';
33
    public const ENTRY_TYPE_OBJECT = 'object';
34
35
    /**
36
     * Entry id for cursor id
37
     */
38
    private const ENTRY_ID = 'id';
39
40
    /**
41
     * Whether or not to get more documents
42
     */
43
    private const ENTRY_HAS_MORE = 'hasMore';
44
45
    /**
46
     * Result documents
47
     */
48
    private const ENTRY_RESULT = 'result';
49
50
    /**
51
     * Extra data
52
     */
53
    private const ENTRY_EXTRA = 'extra';
54
55
    /**
56
     * Stats
57
     */
58
    private const ENTRY_STATS = 'stats';
59
60
    /**
61
     * Full count (ignoring the outermost LIMIT)
62
     */
63
    private const FULL_COUNT = 'fullCount';
64
65
    /**
66
     * Whether or not the result was served from the AQL query cache
67
     */
68
    private const ENTRY_CACHED = 'cached';
69
70
    /**
71
     * @var ClientInterface
72
     */
73
    private $client;
74
75
    /**
76
     * Cursor options
77
     *
78
     * @var array
79
     */
80
    private $options;
81
82
    /**
83
     * @var RequestFactoryInterface
84
     */
85
    private $requestFactory;
86
87
    /**
88
     * @var mixed
89
     */
90
    private $data;
91
92
    /**
93
     * @var bool
94
     */
95
    private $hasMore = true;
96
97
    /**
98
     * cursor id
99
     *
100
     * @var string
101
     */
102
    private $id;
103
104
    /**
105
     * Current position in result set iteration (zero-based)
106
     *
107
     * @var int
108
     */
109
    private $position;
110
111
    /**
112
     * Total length of result set (in number of documents)
113
     *
114
     * @var int
115
     */
116
    private $length;
117
118
    /**
119
     * Full count of the result set (ignoring the outermost LIMIT)
120
     *
121
     * @var int|null
122
     */
123
    private $fullCount;
124
125
    /**
126
     * Extra data (statistics) returned from the statement
127
     *
128
     * @var array
129
     */
130
    private $extra = [];
131
132
    /**
133
     * Number of HTTP calls that were made to build the cursor result
134
     *
135
     * @var int
136
     */
137
    private $fetches = 0;
138
139
    /**
140
     * Whether or not the query result was served from the AQL query result cache
141
     *
142
     * @var bool
143
     */
144
    private $cached = false;
145
146
    /**
147
     * @var RequestInterface
148
     */
149
    private $request;
150
151
    /**
152
     * @var bool
153
     */
154
    private $executed = false;
155
156
    /**
157
     * Query is executed on first access
158
     *
159
     * @param ClientInterface $client - connection to be used
160
     * @param RequestInterface $request Cursor request
161
     * @param RequestFactoryInterface $requestFactory
162
     * @param array $options
163
     */
164 7
    public function __construct(
165
        ClientInterface $client,
166
        RequestInterface $request,
167
        RequestFactoryInterface $requestFactory,
168
        array $options = []
169
    ) {
170 7
        if (! isset($options[self::ENTRY_TYPE])) {
171
            $options[self::ENTRY_TYPE] = self::ENTRY_TYPE_JSON;
172
        }
173
174 7
        $this->client = $client;
175 7
        $this->options = $options;
176 7
        $this->requestFactory = $requestFactory;
177 7
        $this->request = $request;
178 7
        $this->data = [];
179 7
    }
180
181
    /**
182
     * Fetch outstanding results from the server
183
     *
184
     * @return void
185
     * @throws ClientExceptionInterface
186
     */
187 7
    private function fetchOutstanding(): void
188
    {
189 7
        $request = $this->fetches === 0
190 7
            ? $this->request
191 7
            : $this->requestFactory->createRequest(RequestMethodInterface::METHOD_PUT, Url::CURSOR . '/' . $this->id);
192
193 7
        $response = $this->client->sendRequest($request);
194
195 7
        $httpStatusCode = $response->getStatusCode();
196
197 7
        if ($httpStatusCode < StatusCodeInterface::STATUS_OK
198 7
            || $httpStatusCode > StatusCodeInterface::STATUS_MULTIPLE_CHOICES
199
        ) {
200
            throw ServerException::with($request, $response);
201
        }
202
203 7
        $this->fetches++;
204
205 7
        $data = $response->getBody();
206 7
        $tmp = $data->getContents();
207 7
        $data = $data instanceof JsonStream ? $data->toArray() : Json::decode($tmp);
208
209 7
        if (isset($data[self::ENTRY_ID])) {
210 3
            $this->id = $data[self::ENTRY_ID];
211
        }
212
213 7
        if (isset($data[self::ENTRY_EXTRA])) {
214 7
            $this->extra = $data[self::ENTRY_EXTRA];
215
216 7
            if (isset($this->extra[self::ENTRY_STATS][self::FULL_COUNT])) {
217
                $this->fullCount = $this->extra[self::ENTRY_STATS][self::FULL_COUNT];
218
            }
219
        }
220
221 7
        if (isset($data[self::ENTRY_CACHED])) {
222 7
            $this->cached = $data[self::ENTRY_CACHED];
223
        }
224 7
        $this->hasMore = $data[self::ENTRY_HAS_MORE] ?? false;
225
226 7
        $this->length += count($data[self::ENTRY_RESULT]);
227
228 7
        $this->data = array_merge($this->data, $data[self::ENTRY_RESULT]);
229
230 7
        if (false === $this->hasMore) {
231 7
            unset($this->id);
232
        }
233 7
    }
234
235
    /**
236
     * Return the current result row depending on entry type
237
     *
238
     * This might issue additional HTTP requests to fetch any outstanding results from the server
239
     *
240
     * @return string|array|object Data
241
     * @throws ClientExceptionInterface
242
     */
243 5
    public function fetchAll()
244
    {
245 5
        while ($this->hasMore) {
246 5
            $this->fetchOutstanding();
247
        }
248
249 5
        switch ($this->options[self::ENTRY_TYPE]) {
250 5
            case self::ENTRY_TYPE_OBJECT:
251
                return (object)$this->data;
252
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
253 5
            case self::ENTRY_TYPE_ARRAY:
254 3
                return $this->data;
255
                break;
256 2
            case self::ENTRY_TYPE_JSON:
257
            default:
258 2
                return Json::encode($this->data);
259
                break;
260
        }
261
    }
262
263
    /**
264
     * Get the total number of results in the cursor.
265
     *
266
     * This might issue additional HTTP requests to fetch any outstanding results from the server.
267
     *
268
     * @return int Total number of results
269
     * @throws ClientExceptionInterface
270
     */
271
    public function count()
272
    {
273
        while ($this->hasMore) {
274
            $this->fetchOutstanding();
275
        }
276
277
        return $this->length;
278
    }
279
280
    /**
281
     * Rewind the cursor, loads first batch, can be repeated (new cursor will be created)
282
     *
283
     * @return void
284
     * @throws ClientExceptionInterface
285
     */
286 3
    public function rewind()
287
    {
288 3
        $this->length = 0;
289 3
        $this->fetches = 0;
290 3
        $this->position = 0;
291 3
        $this->executed = false;
292 3
        $this->hasMore = true;
293
294 3
        $this->data = [];
295 3
        $this->fetchOutstanding();
296 3
    }
297
298
    /**
299
     * Return the current result row depending on entry type
300
     *
301
     * @return string|array|object Data
302
     */
303 3
    public function current()
304
    {
305 3
        switch ($this->options[self::ENTRY_TYPE]) {
306 3
            case self::ENTRY_TYPE_OBJECT:
307
                return (object)$this->data[$this->position];
308 3
            case self::ENTRY_TYPE_ARRAY:
309 3
                return $this->data[$this->position];
310
            case self::ENTRY_TYPE_JSON:
311
            default:
312
                return Json::encode($this->data[$this->position]);
313
        }
314
    }
315
316 3
    public function key(): int
317
    {
318 3
        return $this->position;
319
    }
320
321 3
    public function next(): void
322
    {
323 3
        $this->position++;
324 3
    }
325
326
    /**
327
     * @return bool
328
     * @throws ClientExceptionInterface
329
     */
330 3
    public function valid(): bool
331
    {
332 3
        if ($this->position <= $this->length - 1) {
333
            // we have more results than the current position is
334 3
            return true;
335
        }
336
337 3
        if (! $this->hasMore || $this->id === null) {
338 3
            return false;
339
        }
340
341
        // need to fetch additional results from the server
342 2
        $this->fetchOutstanding();
343
344 2
        return ($this->position <= $this->length - 1);
345
    }
346
347
    /**
348
     * Returns the extra data of the query (statistics etc.). Contents of the result array depend on the type of query
349
     * executed
350
     *
351
     * @return array
352
     */
353
    public function extra(): array
354
    {
355
        return $this->extra ?? [];
356
    }
357
358
    /**
359
     * Returns the warnings issued during query execution
360
     *
361
     * @return array
362
     */
363
    public function warnings(): array
364
    {
365
        return $this->extra['warnings'] ?? [];
366
    }
367
368
    /**
369
     * Returns the number of writes executed by the query
370
     *
371
     * @return int
372
     */
373
    public function writesExecuted(): int
374
    {
375
        return $this->getStatValue('writesExecuted');
376
    }
377
378
    /**
379
     * Returns the number of ignored write operations from the query
380
     *
381
     * @return int
382
     */
383
    public function writesIgnored(): int
384
    {
385
        return $this->getStatValue('writesIgnored');
386
    }
387
388
    /**
389
     * Returns the number of documents iterated over in full scans
390
     *
391
     * @return int
392
     */
393
    public function scannedFull(): int
394
    {
395
        return $this->getStatValue('scannedFull');
396
    }
397
398
    /**
399
     * Returns the number of documents iterated over in index scans
400
     *
401
     * @return int
402
     */
403
    public function scannedIndex(): int
404
    {
405
        return $this->getStatValue('scannedIndex');
406
    }
407
408
    /**
409
     * Returns the number of documents filtered by the query
410
     *
411
     * @return int
412
     */
413
    public function filtered(): int
414
    {
415
        return $this->getStatValue('filtered');
416
    }
417
418
    /**
419
     * Returns the number of HTTP calls that were made to build the cursor result
420
     *
421
     * @return int
422
     */
423 7
    public function fetches(): int
424
    {
425 7
        return $this->fetches;
426
    }
427
428
    /**
429
     * Returns cursor id only after first rewind / fetch
430
     *
431
     * @return string
432
     */
433
    public function getId(): ?string
434
    {
435
        return $this->id;
436
    }
437
438
    /**
439
     * Get the full count of the cursor if available. Does not load all data.
440
     *
441
     * @return int Total number of results
442
     */
443
    public function fullCount(): ?int
444
    {
445
        return $this->fullCount;
446
    }
447
448
    /**
449
     * Get the cached attribute for the result set
450
     *
451
     * @return bool Whether or not the query result was served from the AQL query cache
452
     */
453
    public function isCached(): bool
454
    {
455
        return $this->cached;
456
    }
457
458
    /**
459
     * Returns statistical figure value from the query result, default is 0
460
     *
461
     * @param string $name Name of figure
462
     *
463
     * @return int
464
     */
465
    private function getStatValue(string $name): int
466
    {
467
        return $this->extra[self::ENTRY_STATS][$name] ?? 0;
468
    }
469
}
470