Issues (83)

src/Kernel/BaseClient.php (2 issues)

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
namespace EasyWeChat\Kernel;
13
14
use EasyWeChat\Kernel\Contracts\AccessTokenInterface;
15
use EasyWeChat\Kernel\Http\Response;
16
use EasyWeChat\Kernel\Traits\HasHttpRequests;
17
use GuzzleHttp\MessageFormatter;
18
use GuzzleHttp\Middleware;
19
use Psr\Http\Message\RequestInterface;
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Log\LogLevel;
22
23
/**
24
 * Class BaseClient.
25
 *
26
 * @author overtrue <[email protected]>
27
 */
28
class BaseClient
29
{
30
    use HasHttpRequests {
31
        request as performRequest;
32
    }
33
34
    /**
35
     * @var \EasyWeChat\Kernel\ServiceContainer
36
     */
37
    protected $app;
38
    /**
39
     * @var \EasyWeChat\Kernel\Contracts\AccessTokenInterface|null
40
     */
41
    protected $accessToken = null;
42
    /**
43
     * @var string
44
     */
45
    protected $baseUri;
46
47
    /**
48
     * BaseClient constructor.
49
     *
50
     * @param \EasyWeChat\Kernel\ServiceContainer                    $app
51
     * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface|null $accessToken
52
     */
53 592
    public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null)
54
    {
55 592
        $this->app = $app;
56 592
        $this->accessToken = $accessToken ?? $this->app['access_token'];
57 592
    }
58
59
    /**
60
     * GET request.
61
     *
62
     * @param string $url
63
     * @param array  $query
64
     *
65
     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
66
     *
67
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
68
     * @throws \GuzzleHttp\Exception\GuzzleException
69
     */
70 1
    public function httpGet(string $url, array $query = [])
71
    {
72 1
        return $this->request($url, 'GET', ['query' => $query]);
73
    }
74
75
    /**
76
     * POST request.
77
     *
78
     * @param string $url
79
     * @param array  $data
80
     *
81
     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
82
     *
83
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
84
     * @throws \GuzzleHttp\Exception\GuzzleException
85
     */
86 1
    public function httpPost(string $url, array $data = [])
87
    {
88 1
        return $this->request($url, 'POST', ['form_params' => $data]);
89
    }
90
91
    /**
92
     * JSON request.
93
     *
94
     * @param string $url
95
     * @param array  $data
96
     * @param array  $query
97
     *
98
     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
99
     *
100
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
101
     * @throws \GuzzleHttp\Exception\GuzzleException
102
     */
103 1
    public function httpPostJson(string $url, array $data = [], array $query = [])
104
    {
105 1
        return $this->request($url, 'POST', ['query' => $query, 'json' => $data]);
106
    }
107
108
    /**
109
     * Upload file.
110
     *
111
     * @param string $url
112
     * @param array  $files
113
     * @param array  $form
114
     * @param array  $query
115
     *
116
     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
117
     *
118
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
119
     * @throws \GuzzleHttp\Exception\GuzzleException
120
     */
121 1
    public function httpUpload(string $url, array $files = [], array $form = [], array $query = [])
122
    {
123 1
        $multipart = [];
124
125 1
        foreach ($files as $name => $path) {
126 1
            $multipart[] = [
127 1
                'name' => $name,
128 1
                'contents' => fopen($path, 'r'),
129
            ];
130
        }
131
132 1
        foreach ($form as $name => $contents) {
133 1
            $multipart[] = compact('name', 'contents');
134
        }
135
136 1
        return $this->request(
137 1
            $url,
138 1
            'POST',
139 1
            ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30]
140
        );
141
    }
142
143
    /**
144
     * @return AccessTokenInterface
145
     */
146 1
    public function getAccessToken(): AccessTokenInterface
147
    {
148 1
        return $this->accessToken;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->accessToken could return the type null which is incompatible with the type-hinted return EasyWeChat\Kernel\Contracts\AccessTokenInterface. Consider adding an additional type-check to rule them out.
Loading history...
149
    }
150
151
    /**
152
     * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface $accessToken
153
     *
154
     * @return $this
155
     */
156 1
    public function setAccessToken(AccessTokenInterface $accessToken)
157
    {
158 1
        $this->accessToken = $accessToken;
159
160 1
        return $this;
161
    }
162
163
    /**
164
     * @param string $url
165
     * @param string $method
166
     * @param array  $options
167
     * @param bool   $returnRaw
168
     *
169
     * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
170
     *
171
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
172
     * @throws \GuzzleHttp\Exception\GuzzleException
173
     */
174 1
    public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false)
175
    {
176 1
        if (empty($this->middlewares)) {
177 1
            $this->registerHttpMiddlewares();
178
        }
179
180 1
        $response = $this->performRequest($url, $method, $options);
181
182 1
        $this->app->events->dispatch(new Events\HttpResponseCreated($response));
183
184 1
        return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
185
    }
186
187
    /**
188
     * @param string $url
189
     * @param string $method
190
     * @param array  $options
191
     *
192
     * @return \EasyWeChat\Kernel\Http\Response
193
     *
194
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
195
     * @throws \GuzzleHttp\Exception\GuzzleException
196
     */
197 1
    public function requestRaw(string $url, string $method = 'GET', array $options = [])
198
    {
199 1
        return Response::buildFromPsrResponse($this->request($url, $method, $options, true));
200
    }
201
202
    /**
203
     * Register Guzzle middlewares.
204
     */
205 1
    protected function registerHttpMiddlewares()
206
    {
207
        // retry
208 1
        $this->pushMiddleware($this->retryMiddleware(), 'retry');
209
        // access token
210 1
        $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token');
211
        // log
212 1
        $this->pushMiddleware($this->logMiddleware(), 'log');
213 1
    }
214
215
    /**
216
     * Attache access token to request query.
217
     *
218
     * @return \Closure
219
     */
220 1
    protected function accessTokenMiddleware()
221
    {
222 1
        return function (callable $handler) {
223 1
            return function (RequestInterface $request, array $options) use ($handler) {
224 1
                if ($this->accessToken) {
225 1
                    $request = $this->accessToken->applyToRequest($request, $options);
226
                }
227
228 1
                return $handler($request, $options);
229 1
            };
230 1
        };
231
    }
232
233
    /**
234
     * Log the request.
235
     *
236
     * @return \Closure
237
     */
238 1
    protected function logMiddleware()
239
    {
240 1
        $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG);
241
242 1
        return Middleware::log($this->app['logger'], $formatter, LogLevel::DEBUG);
243
    }
244
245
    /**
246
     * Return retry middleware.
247
     *
248
     * @return \Closure
249
     */
250 1
    protected function retryMiddleware()
251
    {
252 1
        return Middleware::retry(
253 1
            function (
254
                $retries,
255
                RequestInterface $request,
256
                ResponseInterface $response = null
257
            ) {
258
                // Limit the number of retries to 2
259 1
                if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) {
260
                    // Retry on server errors
261 1
                    $response = json_decode($body, true);
262
263 1
                    if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) {
264 1
                        $this->accessToken->refresh();
0 ignored issues
show
The method refresh() does not exist on null. ( Ignorable by Annotation )

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

264
                        $this->accessToken->/** @scrutinizer ignore-call */ 
265
                                            refresh();

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...
265 1
                        $this->app['logger']->debug('Retrying with refreshed access token.');
266
267 1
                        return true;
268
                    }
269
                }
270
271 1
                return false;
272 1
            },
273 1
            function () {
274 1
                return abs($this->app->config->get('http.retry_delay', 500));
275 1
            }
276
        );
277
    }
278
}
279