Test Failed
Push — master ( 0a62d9...bdd8d4 )
by Carlos
02:27
created

AbstractAPI::maxRetries()   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
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the overtrue/wechat.
5
 *
6
 * (c) overtrue <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
/**
13
 * AbstractAPI.php.
14
 *
15
 * This file is part of the wechat-components.
16
 *
17
 * (c) overtrue <[email protected]>
18
 *
19
 * This source file is subject to the MIT license that is bundled
20
 * with this source code in the file LICENSE.
21
 */
22
23
namespace EasyWeChat\Core;
24
25
use EasyWeChat\Core\Exceptions\HttpException;
26
use EasyWeChat\Support\Collection;
27
use EasyWeChat\Support\Log;
28
use GuzzleHttp\Middleware;
29
use GuzzleHttp\Psr7\Uri;
30
use Psr\Http\Message\RequestInterface;
31
use Psr\Http\Message\ResponseInterface;
32
33
/**
34
 * Class AbstractAPI.
35
 */
36
abstract class AbstractAPI
37
{
38
    /**
39
     * Http instance.
40
     *
41
     * @var \EasyWeChat\Core\Http
42
     */
43
    protected $http;
44
45
    /**
46
     * The request token.
47
     *
48
     * @var \EasyWeChat\Core\AccessToken
49
     */
50
    protected $accessToken;
51
52
    const GET = 'get';
53
    const POST = 'post';
54
    const JSON = 'json';
55
56
    /**
57
     * @var int
58
     */
59
    protected static $maxRetries = 2;
60
61 208
    /**
62
     * Constructor.
63 208
     *
64 208
     * @param \EasyWeChat\Core\AccessToken $accessToken
65
     */
66
    public function __construct(AccessToken $accessToken)
67
    {
68
        $this->setAccessToken($accessToken);
69
    }
70
71 9
    /**
72
     * Return the http instance.
73 9
     *
74 1
     * @return \EasyWeChat\Core\Http
75 1
     */
76
    public function getHttp()
77 9
    {
78 8
        if (is_null($this->http)) {
79 8
            $this->http = new Http();
80
        }
81 9
82
        if (count($this->http->getMiddlewares()) === 0) {
83
            $this->registerHttpMiddlewares();
84
        }
85
86
        return $this->http;
87
    }
88
89
    /**
90
     * Set the http instance.
91 13
     *
92
     * @param \EasyWeChat\Core\Http $http
93 13
     *
94
     * @return $this
95 13
     */
96
    public function setHttp(Http $http)
97
    {
98
        $this->http = $http;
99
100
        return $this;
101
    }
102
103 20
    /**
104
     * Return the current accessToken.
105 20
     *
106
     * @return \EasyWeChat\Core\AccessToken
107
     */
108
    public function getAccessToken()
109
    {
110
        return $this->accessToken;
111
    }
112
113
    /**
114
     * Set the request token.
115 208
     *
116
     * @param \EasyWeChat\Core\AccessToken $accessToken
117 208
     *
118
     * @return $this
119 208
     */
120
    public function setAccessToken(AccessToken $accessToken)
121
    {
122
        $this->accessToken = $accessToken;
123
124
        return $this;
125
    }
126
127
    /**
128
     * @param int $retries
129
     */
130 7
    public static function maxRetries($retries)
131
    {
132 7
        self::$maxRetries = abs($retries);
0 ignored issues
show
Documentation Bug introduced by
It seems like abs($retries) can also be of type double. However, the property $maxRetries is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
133
    }
134 7
135
    /**
136 7
     * Parse JSON from response and check error.
137
     *
138 7
     * @param string $method
139
     * @param array  $args
140
     *
141
     * @return \EasyWeChat\Support\Collection
142
     */
143
    public function parseJSON($method, array $args)
144 8
    {
145
        $http = $this->getHttp();
146
147 8
        $contents = $http->parseJSON(call_user_func_array([$http, $method], $args));
148
149 8
        $this->checkAndThrow($contents);
150
151 8
        return new Collection($contents);
152 8
    }
153
154
    /**
155
     * Register Guzzle middlewares.
156
     */
157
    protected function registerHttpMiddlewares()
158
    {
159 8
        // log
160
        $this->http->addMiddleware($this->logMiddleware());
161
        // retry
162
        $this->http->addMiddleware($this->retryMiddleware());
163
        // access token
164
        $this->http->addMiddleware($this->accessTokenMiddleware());
165
    }
166
167
    /**
168
     * Attache access token to request query.
169
     *
170
     * @return \Closure
171
     */
172
    protected function accessTokenMiddleware()
173
    {
174 8
        return function (callable $handler) {
175
            return function (RequestInterface $request, array $options) use ($handler) {
176
                if (!$this->accessToken) {
177
                    return $handler($request, $options);
178
                }
179
180
                $field = $this->accessToken->getQueryName();
181
                $token = $this->accessToken->getToken();
182 8
183
                $request = $request->withUri(Uri::withQueryValue($request->getUri(), $field, $token));
184
185
                return $handler($request, $options);
186
            };
187 8
        };
188
    }
189
190
    /**
191
     * Log the request.
192
     *
193
     * @return \Closure
194
     */
195
    protected function logMiddleware()
196
    {
197 8
        return Middleware::tap(function (RequestInterface $request, $options) {
198
            Log::debug("Request: {$request->getMethod()} {$request->getUri()} ".json_encode($options));
199
            Log::debug('Request headers:'.json_encode($request->getHeaders()));
200
        });
201
    }
202
203
    /**
204
     * Return retry middleware.
205
     *
206
     * @return \Closure
207
     */
208
    protected function retryMiddleware()
209
    {
210
        return Middleware::retry(function (
211
                                          $retries,
212
                                          RequestInterface $request,
213
                                          ResponseInterface $response = null
214
                                       ) {
215
            // Limit the number of retries to 2
216
            if ($retries <= self::$maxRetries && $response && $body = $response->getBody()) {
217
                // Retry on server errors
218
                if (stripos($body, 'errcode') && (stripos($body, '40001') || stripos($body, '42001'))) {
219 8
                    $field = $this->accessToken->getQueryName();
220
                    $token = $this->accessToken->getToken(true);
221
222
                    $request = $request->withUri($newUri = Uri::withQueryValue($request->getUri(), $field, $token));
223
224
                    Log::debug("Retry with Request Token: {$token}");
225
                    Log::debug("Retry with Request Uri: {$newUri}");
226
227
                    return true;
228
                }
229 8
            }
230
231 8
            return false;
232 1
        });
233 1
    }
234 1
235
    /**
236 1
     * Check the array data errors, and Throw exception when the contents contains error.
237
     *
238 8
     * @param array $contents
239
     *
240
     * @throws \EasyWeChat\Core\Exceptions\HttpException
241
     */
242
    protected function checkAndThrow(array $contents)
243
    {
244
        if (isset($contents['errcode']) && 0 !== $contents['errcode']) {
245
            if (empty($contents['errmsg'])) {
246
                $contents['errmsg'] = 'Unknown';
247
            }
248
249
            throw new HttpException($contents['errmsg'], $contents['errcode']);
250
        }
251
    }
252
}
253