Completed
Push — develop ( fbac2a...fa8907 )
by Edwin
04:58
created

Request::getCallLimit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace ShopifyClient;
4
5
use GuzzleHttp\ClientInterface;
6
use GuzzleHttp\Exception\ClientException;
7
use ShopifyClient\Exception\ShopifyException;
8
9
class Request
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 string
20
     */
21
    protected $responseKey;
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 static $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
     * Request constructor.
50
     * @param ClientInterface $client
51
     */
52 5
    public function __construct(ClientInterface $client)
53
    {
54 5
        $this->httpClient = $client;
0 ignored issues
show
Documentation Bug introduced by
It seems like $client of type object<GuzzleHttp\ClientInterface> is incompatible with the declared type object<ShopifyClient\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...
55 5
    }
56
57
    /**
58
     * @param string $method
59
     * @param string $endpoint
60
     * @param array $parameters
61
     * @return bool
62
     * @throws ShopifyException
63
     */
64 130
    public function request(string $method, string $endpoint, array $parameters = [])
65
    {
66 130
        $this->handleRateLimit();
67
68
        try {
69 130
            $response = $this->httpClient->request($method, $endpoint, $parameters);
0 ignored issues
show
Bug introduced by
The method request() does not seem to exist on object<ShopifyClient\Client>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
70 3
        } catch (ClientException $e) {
71 3
            $response = $e->getResponse();
72 3
            $content  = json_decode($response->getBody()->getContents(), true);
73
74 3
            throw new ShopifyException($content['errors'], $response->getStatusCode(), $e);
75
        }
76
77 127
        $this->setRateLimit($response->getHeader(self::API_CALL_LIMIT_HEADER));
78
79 127
        $data = json_decode($response->getBody()->getContents(), true);
80
81 127
        if (strlen($this->responseKey) > 0) {
82 105
            return $data[$this->responseKey];
83
        } else {
84 23
            return true;
85
        }
86
    }
87
88
    /**
89
     * @param string $responseKey
90
     * @return Request
91
     */
92 132
    public function setResponseKey($responseKey)
93
    {
94 132
        $this->responseKey = $responseKey;
95 132
    }
96
97
    /**
98
     * @param callable $function
99
     * @return mixed
100
     */
101 15
    public static function throttle(callable $function)
102
    {
103 15
        $start    = time();
104 15
        $result   = $function();
105 15
        $end      = time();
106 15
        $duration = $end - $start;
107 15
        $waitTime = ceil(static::$callCycle - $duration);
0 ignored issues
show
Bug introduced by
Since $callCycle is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $callCycle to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
108
109 15
        if ($waitTime > 0) {
110 11
            sleep($waitTime);
111
        }
112
113 15
        return $result;
114
    }
115
116
    /**
117
     * @return int
118
     */
119 1
    public function getRateLimitReached(): int
120
    {
121 1
        return $this->rateLimitReached;
122
    }
123
124 130
    private function handleRateLimit()
125
    {
126 130
        if ($this->callsMade > 0 && $this->isRateLimitReached()) {
127 6
            $this->rateLimitReached++;
128
            // Prevent bucket overflow
129
            // https://help.shopify.com/api/getting-started/api-call-limit
130 6
            usleep(rand(3, 10) * 1000000);
131
        }
132 130
    }
133
134
    /**
135
     * @param array $header
136
     */
137 127
    private function setRateLimit(array $header)
138
    {
139 127
        if (empty($header)) {
140 1
            return;
141
        }
142
143 126
        $parts           = explode('/', $header[0]);
144 126
        $this->rateLimit = $parts[1];
145 126
        $this->callsMade = $parts[0];
146 126
    }
147
148
    /**
149
     * @return bool
150
     */
151 122
    public function isRateLimitReached(): bool
152
    {
153 122
        return $this->getCallLimit() >= $this->rateLimitThreshold;
154
    }
155
156
    /**
157
     * @return float|int
158
     */
159 122
    public function getCallLimit()
160
    {
161 122
        return $this->callsMade / $this->rateLimit;
162
    }
163
}
164