Completed
Pull Request — master (#590)
by
unknown
03:20
created

AbstractAPI   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 204
ccs 39
cts 39
cp 1
rs 10
c 0
b 0
f 0
wmc 23
lcom 1
cbo 8

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setHttp() 0 6 1
A getAccessToken() 0 4 1
A setAccessToken() 0 6 1
A parseJSON() 0 10 1
A checkAndThrow() 0 10 4
A registerHttpMiddlewares() 0 9 1
A getHttp() 0 12 3
A accessTokenMiddleware() 0 17 2
A logMiddleware() 0 7 1
C retryMiddleware() 0 26 7
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
     * Constructor.
58
     *
59
     * @param \EasyWeChat\Core\AccessToken $accessToken
60
     */
61 172
    public function __construct(AccessToken $accessToken)
62
    {
63 172
        $this->setAccessToken($accessToken);
64 172
    }
65
66
    /**
67
     * Return the http instance.
68
     *
69
     * @return \EasyWeChat\Core\Http
70
     */
71 9
    public function getHttp()
72
    {
73 9
        if (is_null($this->http)) {
74 1
            $this->http = new Http();
75
        }
76
77 9
        if (count($this->http->getMiddlewares()) === 0) {
78 8
            $this->registerHttpMiddlewares();
79
        }
80
81 9
        return $this->http;
82
    }
83
84
    /**
85
     * Set the http instance.
86
     *
87
     * @param \EasyWeChat\Core\Http $http
88
     *
89
     * @return $this
90
     */
91 13
    public function setHttp(Http $http)
92
    {
93 13
        $this->http = $http;
94
95 13
        return $this;
96
    }
97
98
    /**
99
     * Return the current accessToken.
100
     *
101
     * @return \EasyWeChat\Core\AccessToken
102
     */
103 8
    public function getAccessToken()
104
    {
105 8
        return $this->accessToken;
106
    }
107
108
    /**
109
     * Set the request token.
110
     *
111
     * @param \EasyWeChat\Core\AccessToken $accessToken
112
     *
113
     * @return $this
114
     */
115 172
    public function setAccessToken(AccessToken $accessToken)
116
    {
117 172
        $this->accessToken = $accessToken;
118
119 172
        return $this;
120
    }
121
122
    /**
123
     * Parse JSON from response and check error.
124
     *
125
     * @param string $method
126
     * @param array  $args
127
     *
128
     * @return \EasyWeChat\Support\Collection
129
     */
130 7
    public function parseJSON($method, array $args)
131
    {
132 7
        $http = $this->getHttp();
133
134 7
        $contents = $http->parseJSON(call_user_func_array([$http, $method], $args));
135
136 7
        $this->checkAndThrow($contents);
137
138 7
        return new Collection($contents);
139
    }
140
141
    /**
142
     * Register Guzzle middlewares.
143
     */
144 8
    protected function registerHttpMiddlewares()
145
    {
146
        // log
147 8
        $this->http->addMiddleware($this->logMiddleware());
148
        // retry
149 8
        $this->http->addMiddleware($this->retryMiddleware());
150
        // access token
151 8
        $this->http->addMiddleware($this->accessTokenMiddleware());
152 8
    }
153
154
    /**
155
     * Attache access token to request query.
156
     *
157
     * @return \Closure
158
     */
159 8
    protected function accessTokenMiddleware()
160
    {
161
        return function (callable $handler) {
162
            return function (RequestInterface $request, array $options) use ($handler) {
163
                if (!$this->accessToken) {
164
                    return $handler($request, $options);
165
                }
166
167
                $field = $this->accessToken->getQueryName();
168
                $token = $this->accessToken->getToken();
169
170
                $request = $request->withUri(Uri::withQueryValue($request->getUri(), $field, $token));
171
172
                return $handler($request, $options);
173
            };
174 8
        };
175
    }
176
177
    /**
178
     * Log the request.
179
     *
180
     * @return \Closure
181
     */
182 8
    protected function logMiddleware()
183
    {
184
        return Middleware::tap(function (RequestInterface $request, $options) {
185
            Log::debug("Request: {$request->getMethod()} {$request->getUri()} ".json_encode($options));
186
            Log::debug('Request headers:'.json_encode($request->getHeaders()));
187 8
        });
188
    }
189
190
    /**
191
     * Return retry middleware.
192
     *
193
     * @return \Closure
194
     */
195
    protected function retryMiddleware()
196
    {
197 8
        return Middleware::retry(function (
198
                                          $retries,
199
                                          RequestInterface $request,
200
                                          ResponseInterface $response = null
201
                                       ) {
202
            // Limit the number of retries to 2
203
            if ($retries <= 2 && $response && $body = $response->getBody()) {
204
                // Retry on server errors
205
                if (stripos($body, 'errcode') && (stripos($body, '40001') || stripos($body, '42001'))) {
206
                    $field = $this->accessToken->getQueryName();
207
                    $token = $this->accessToken->getToken(true);
208
209
                    $request = $request->withUri($newUri = Uri::withQueryValue($request->getUri(), $field, $token));
210
211
                    Log::debug("Retry with Request Token: {$token}");
212
                    Log::debug("Retry with Request Uri: {$newUri}");
213
214
                    return true;
215
                }
216
            }
217
218
            return false;
219 8
        });
220
    }
221
222
    /**
223
     * Check the array data errors, and Throw exception when the contents contains error.
224
     *
225
     * @param array $contents
226
     *
227
     * @throws \EasyWeChat\Core\Exceptions\HttpException
228
     */
229 8
    protected function checkAndThrow(array $contents)
230
    {
231 8
        if (isset($contents['errcode']) && 0 !== $contents['errcode']) {
232 1
            if (empty($contents['errmsg'])) {
233 1
                $contents['errmsg'] = 'Unknown';
234
            }
235
236 1
            throw new HttpException($contents['errmsg'], $contents['errcode']);
237
        }
238 8
    }
239
}
240