Passed
Branch feature/first-release (57b0a8)
by Andrea Marco
13:40
created

HandlesTotalPages::chunkPages()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 6
rs 10
cc 4
nc 4
nop 2
1
<?php
2
3
namespace Cerbero\LazyJsonPages\Concerns;
4
5
use Cerbero\LazyJsonPages\Outcome;
6
use GuzzleHttp\Client;
7
use GuzzleHttp\Pool;
8
use GuzzleHttp\Psr7\Uri;
9
use Illuminate\Support\LazyCollection;
10
use Psr\Http\Message\ResponseInterface;
11
use Throwable;
12
use Traversable;
13
14
/**
15
 * The trait to handle APIs with the number of total pages.
16
 *
17
 */
18
trait HandlesTotalPages
19
{
20
    /**
21
     * Handle APIs with the given number of total pages
22
     *
23
     * @param int $pages
24
     * @param Uri|null $uri
25
     * @return Traversable
26
     */
27
    protected function handleByTotalPages(int $pages, Uri $uri = null): Traversable
28
    {
29
        $uri = $uri ?: $this->config->source->request->getUri();
30
        $firstPageAlreadyFetched = strval($uri) == strval($this->config->source->request->getUri());
31
        $chunkedPages = $this->chunkPages($pages, $firstPageAlreadyFetched);
32
        $items = $this->fetchItemsAsynchronously($chunkedPages, $uri);
33
34
        if ($firstPageAlreadyFetched) {
35
            yield from $this->config->source->json($this->config->path);
36
        }
37
38
        yield from $items;
39
    }
40
41
    /**
42
     * Retrieve the given pages in chunks
43
     *
44
     * @param int $pages
45
     * @param bool $skipFirstPage
46
     * @return iterable
47
     */
48
    protected function chunkPages(int $pages, bool $skipFirstPage): iterable
49
    {
50
        $firstPage = $skipFirstPage ? $this->config->firstPage + 1 : $this->config->firstPage;
51
        $lastPage = $this->config->firstPage == 0 ? $pages - 1 : $pages;
52
53
        return LazyCollection::range($firstPage, $lastPage)->chunk($this->config->chunk ?: INF);
0 ignored issues
show
Bug introduced by
It seems like $this->config->chunk ?: ...yJsonPages\Concerns\INF can also be of type double; however, parameter $size of Illuminate\Support\LazyCollection::chunk() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

53
        return LazyCollection::range($firstPage, $lastPage)->chunk(/** @scrutinizer ignore-type */ $this->config->chunk ?: INF);
Loading history...
54
    }
55
56
    /**
57
     * Fetch items by performing asynchronous HTTP calls
58
     *
59
     * @param iterable $chunkedPages
60
     * @param Uri $uri
61
     * @return Traversable
62
     */
63
    protected function fetchItemsAsynchronously(iterable $chunkedPages, Uri $uri): Traversable
64
    {
65
        $client = new Client(['timeout' => $this->config->timeout]);
66
67
        foreach ($chunkedPages as $pages) {
68
            $outcome = $this->retry(function (Outcome $outcome) use ($uri, $client, $pages) {
0 ignored issues
show
Bug introduced by
It seems like retry() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

68
            /** @scrutinizer ignore-call */ 
69
            $outcome = $this->retry(function (Outcome $outcome) use ($uri, $client, $pages) {
Loading history...
69
                $pages = $outcome->pullFailedPages() ?: $pages;
70
71
                return $this->pool($client, $outcome, function () use ($uri, $pages) {
72
                    $request = clone $this->config->source->request;
73
74
                    foreach ($pages as $page) {
75
                        yield $page => $request->withUri(Uri::withQueryValue($uri, $this->config->pageName, $page));
76
                    }
77
                });
78
            });
79
80
            yield from $outcome->pullItems();
81
        }
82
    }
83
84
    /**
85
     * Retrieve the outcome of a pool of asynchronous requests
86
     *
87
     * @param Client $client
88
     * @param Outcome $outcome
89
     * @param callable $getRequests
90
     * @return Outcome
91
     */
92
    protected function pool(Client $client, Outcome $outcome, callable $getRequests): Outcome
93
    {
94
        $pool = new Pool($client, $getRequests(), [
95
            'concurrency' => $this->config->concurrency,
96
            'fulfilled' => function (ResponseInterface $response, int $page) use ($outcome) {
97
                $outcome->addItemsFromPage($page, $response, $this->config->path);
98
            },
99
            'rejected' => function (Throwable $e, int $page) use ($outcome) {
100
                $outcome->addFailedPage($page);
101
                throw $e;
102
            }
103
        ]);
104
105
        $pool->promise()->wait();
106
107
        return $outcome;
108
    }
109
}
110