Test Failed
Push — develop ( 9e3f9b...05a047 )
by Edwin
02:28
created

AbstractResource   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 15
lcom 2
cbo 5
dl 0
loc 161
ccs 43
cts 43
cp 1
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A request() 0 17 2
A getRateLimitReached() 0 3 1
A getRequestParameters() 0 8 2
A handleRateLimit() 0 9 3
A throttle() 0 17 2
A setRateLimit() 0 7 1
A isRateLimitReached() 0 4 1
A getCallLimit() 0 4 1
A isCountable() 0 4 1
1
<?php
2
3
namespace ShopifyClient\Resource;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\Exception\RequestException;
7
use ShopifyClient\Exception\ClientException;
8
9
abstract class AbstractResource implements Resource
10
{
11
    const API_CALL_LIMIT_HEADER = 'http_x_shopify_shop_api_call_limit';
12
13
    /**
14
     * @var Client
15
     */
16
    protected $httpClient;
17
18
    /**
19
     * @var bool
20
     */
21
    protected $countable = false;
22
23
    /**
24
     * @var int
25
     */
26
    private $rateLimit;
27
28
    /**
29
     * @var int
30
     */
31
    private $callsMade;
32
33
    /**
34
     * @var float
35
     */
36
    private $callCycle = 0.5; // avg. 2 calls a second
37
38
    /**
39
     * @var float
40
     */
41
    private $rateLimitThreshold = 0.8;
42
43
    /**
44
     * @var int
45
     */
46
    private $rateLimitReached = 0;
47
48
    /**
49
     * AbstractResource constructor.
50
     * @param Client $client
51
     */
52 1
    public function __construct(Client $client)
53
    {
54 1
        $this->httpClient = $client;
55 1
    }
56
57
    /**
58
     * @param $method
59
     * @param $endpoint
60
     * @param array $params
61
     * @return array
62
     * @throws ClientException
63
     */
64 42
    public function request($method, $endpoint, $params = [])
65
    {
66 42
        $this->handleRateLimit();
67
68
        try {
69 42
            $response = $this->httpClient->request($method, $endpoint, $this->getRequestParameters($method, $params));
70 2
        } catch (RequestException $e) {
71 2
            $response = $e->getResponse();
72 2
            $content  = json_decode($response->getBody()->getContents(), true);
73
74 2
            throw new ClientException($content['errors'], $response->getStatusCode());
75
        }
76
77 40
        $this->setRateLimit($response->getHeader(self::API_CALL_LIMIT_HEADER));
78
79 40
        return json_decode($response->getBody()->getContents(), true);
80
    }
81
82
    /**
83
     * @return int
84
     */
85 1
    public function getRateLimitReached(): int {
86 1
        return $this->rateLimitReached;
87
    }
88
89
    /**
90
     * @param string $method
91
     * @param array $params
92
     * @return array
93
     */
94 42
    private function getRequestParameters(string $method, array $params): array
95
    {
96 42
        if ($method !== 'GET') {
97 26
            $params['headers']['Content-Type'] = 'application/json';
98
        }
99
100 42
        return $params;
101
    }
102
103 42
    private function handleRateLimit()
104
    {
105 42
        if ($this->callsMade > 0 && $this->isRateLimitReached()) {
106 1
            $this->rateLimitReached++;
107
            // Prevent bucket overflow
108
            // https://help.shopify.com/api/getting-started/api-call-limit
109 1
            usleep(rand(3, 10) * 1000000);
110
        }
111 42
    }
112
113
    /**
114
     * With a "leak rate" of 2 calls per second that continually empties the bucket.
115
     * If your app averages 2 calls per second, it will never trip a 429 error ("bucket overflow").
116
     *
117
     * @param callable $function
118
     * @return mixed
119
     */
120 5
    public function throttle(callable $function)
121
    {
122 5
        $start = time();
123
124 5
        $result = $function();
125
126 5
        $end = time();
127
128 5
        $duration = $end - $start;
129 5
        $waitTime = ceil($this->callCycle - $duration);
130
131 5
        if ($waitTime > 0) {
132 5
            sleep($waitTime);
133
        }
134
135 5
        return $result;
136
    }
137
138
    /**
139
     * @param array $header
140
     */
141 40
    private function setRateLimit(array $header)
142
    {
143 40
        $parts = explode('/', $header[0]);
144
145 40
        $this->rateLimit = $parts[1];
146 40
        $this->callsMade = $parts[0];
147 40
    }
148
149
    /**
150
     * @return bool
151
     */
152 38
    public function isRateLimitReached(): bool
153
    {
154 38
        return $this->getCallLimit() >= $this->rateLimitThreshold;
155
    }
156
157 38
    public function getCallLimit()
158
    {
159 38
        return $this->callsMade / $this->rateLimit;
160
    }
161
162
    /**
163
     * @return bool
164
     */
165 5
    public function isCountable(): bool
166
    {
167 5
        return $this->countable;
168
    }
169
}
170