Passed
Push — master ( a3de2b...088e51 )
by Yao
01:34
created

Sobot   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 336
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 37
eloc 79
c 2
b 1
f 0
dl 0
loc 336
rs 9.44

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getAppKey() 0 3 1
A httpRequest() 0 18 4
A setTimeout() 0 4 1
A getTimeout() 0 3 1
A getAppId() 0 3 1
A getRequestParam() 0 3 1
A request() 0 19 4
A getHeader() 0 6 2
A getRequestUrl() 0 3 1
A tokenIsInvalid() 0 3 3
B setConfig() 0 15 7
A getContentType() 0 6 3
A addHeader() 0 5 2
A __set() 0 7 2
A __get() 0 8 2
A __call() 0 4 1
1
<?php
2
3
4
namespace MuCTS\Sobot\Contracts;
5
6
7
use Exception;
8
use GuzzleHttp\Client;
9
use GuzzleHttp\Exception\RequestException;
10
use MuCTS\Sobot\Code;
11
use MuCTS\Sobot\Exceptions\ConfigException;
12
use MuCTS\Sobot\Exceptions\NotFoundException;
13
use MuCTS\Sobot\Exceptions\ResponseException;
14
use MuCTS\Sobot\Exceptions\SobotException;
15
use MuCTS\Sobot\Token\Token;
16
use Psr\Http\Message\StreamInterface;
17
18
/**
19
 * Class Sobot
20
 * @property-read Request $request
21
 * @package MuCTS\Sobot\Contracts
22
 */
23
abstract class Sobot
24
{
25
    /** @var array */
26
    private $config;
27
    /** @var string */
28
    private $host;
29
    /** @var Cache|null */
30
    protected $cache;
31
    /** @var int 超时时间 */
32
    protected $timeout = 15;
33
    /** @var array */
34
    protected $headers = [];
35
    /** @var string */
36
    protected $contentType = 'json';
37
    /** @var bool 尝试次数 */
38
    private $updatingToken = false;
39
40
    const METHOD_GET = 'get';
41
    const METHOD_POST = 'post';
42
    const METHOD_PUT = 'put';
43
    const METHOD_DELETE = 'delete';
44
45
    /**
46
     * Sobot constructor.
47
     * @param array $config
48
     * @param Cache|null $cache
49
     * @throws ConfigException
50
     */
51
    public function __construct(array $config, ?Cache $cache = null)
52
    {
53
        $this->setConfig($config);
54
        $this->host = 'https://www.sobot.com';
55
        $this->cache = $cache;
56
    }
57
58
    /**
59
     * 测试配置
60
     *
61
     * @param array $config
62
     * @throws ConfigException
63
     * @author herry.yao <[email protected]>
64
     * @version 1.2.2
65
     * @date 2020-08-04
66
     */
67
    public function setConfig(array $config): void
68
    {
69
        if (!array_key_exists('app_id', $config) || empty($config['app_id'])) {
70
            throw new ConfigException('app id can not be empty');
71
        }
72
        if (!is_string($config['app_id'])) {
73
            throw new ConfigException('please enter the correct app id');
74
        }
75
        if (!array_key_exists('app_key', $config) || empty($config['app_key'])) {
76
            throw new ConfigException('app key can not be empty');
77
        }
78
        if (!is_string($config['app_key'])) {
79
            throw new ConfigException('please enter the correct app key');
80
        }
81
        $this->config = $config;
82
    }
83
84
    /**
85
     * get app id
86
     * @return string
87
     * @author herry.yao <[email protected]>
88
     * @version 1.2.2
89
     * @date 2020-08-04
90
     */
91
    public function getAppId(): string
92
    {
93
        return $this->config['app_id'];
94
    }
95
96
    /**
97
     * get app key
98
     *
99
     * @return string
100
     * @author herry.yao <[email protected]>
101
     * @version 1.2.2
102
     * @date 2020-08-04
103
     */
104
    protected function getAppKey(): string
105
    {
106
        return $this->config['app_key'];
107
    }
108
109
    /**
110
     * 接口请求地址
111
     *
112
     * @return string
113
     * @author herry.yao <[email protected]>
114
     * @version 1.2.2
115
     * @date 2020-08-04
116
     */
117
    protected function getRequestUrl(): string
118
    {
119
        return sprintf('%s/%s', rtrim($this->host), ltrim($this->getRequestPath()));
120
    }
121
122
    /**
123
     * 网络请求
124
     *
125
     * @return StreamInterface
126
     * @throws ConfigException
127
     * @throws ResponseException
128
     * @throws SobotException
129
     * @throws \GuzzleHttp\Exception\GuzzleException
130
     */
131
    protected function httpRequest(): StreamInterface
132
    {
133
        $params = $this->getRequestParam()->toArray();
134
        $method = $this->getRequestMethod();
135
        $client = new Client(["headers" => $this->getHeader(), "timeout" => $this->getTimeout()]);
136
        try {
137
            $response = $client->request($method, $this->getRequestUrl(), [$this->getContentType() => $params]);
138
        } catch (RequestException $exception) {
139
            $response = $exception->getResponse();
140
        } catch (Exception $exception) {
141
            throw  new SobotException($exception->getMessage(), $exception->getCode(), $exception->getPrevious());
142
        }
143
        $status = $response->getStatusCode();
144
        $content = $response->getBody();
145
        if ($status != 200) {
146
            throw new ResponseException('Abnormal response', $status, $content);
147
        }
148
        return $content;
149
    }
150
151
    /**
152
     * 确认是否是Token失效
153
     *
154
     * @param string $retCode
155
     * @return bool
156
     */
157
    protected function tokenIsInvalid(string $retCode)
158
    {
159
        return !$this->updatingToken && $retCode == Code::TOKEN_IS_INVALID && !($this instanceof Token);
160
    }
161
162
    /**
163
     * 接口请求
164
     *
165
     * @return Response
166
     * @throws ResponseException
167
     * @throws SobotException
168
     * @throws \GuzzleHttp\Exception\GuzzleException
169
     * @throws ConfigException
170
     * @throws NotFoundException
171
     * @author herry.yao <[email protected]>
172
     * @version 1.2.2
173
     * @date 2020-08-05
174
     */
175
    public function request(): Response
176
    {
177
        $content = $this->httpRequest();
178
        $res = json_decode($content, true);
179
        $retCode = array_get($res, 'ret_code', Code::SYSTEM_ERR);
180
181
        if ($retCode != Code::SUCCESS_CODE) {
182
            // 如果是TOKEN失效重新获取
183
            if ($this->tokenIsInvalid($retCode)) {
184
                $this->updatingToken = true;
185
                return $this->request();
186
            }
187
            throw new ResponseException(array_get($res, 'ret_msg', 'Sobot Response err'), $retCode, $content);
188
        }
189
        $class = get_class($this) . '\\Response';
190
        if (class_exists($class)) {
191
            return new $class($res);
192
        }
193
        throw new NotFoundException(sprintf('Class[%s] File Not Found.', $class));
194
    }
195
196
    /**
197
     * 获取请求参数传递方式
198
     *
199
     * @return string
200
     * @author herry.yao <[email protected]>
201
     * @version 1.2.2
202
     * @date 2020-08-05
203
     */
204
    protected function getContentType(): string
205
    {
206
        if (strtolower($this->getRequestMethod()) == self::METHOD_GET) {
207
            return 'query';
208
        }
209
        return $this->contentType ?: 'json';
210
    }
211
212
    /**
213
     * 获取头部数据
214
     *
215
     * @return array
216
     * @throws ConfigException
217
     * @throws ResponseException
218
     * @throws SobotException
219
     * @throws \GuzzleHttp\Exception\GuzzleException
220
     * @version 1.2.2
221
     * @date 2020-08-05
222
     * @author herry.yao <[email protected]>
223
     */
224
    protected function getHeader(): array
225
    {
226
        if (!($this instanceof Token)) {
227
            $this->addHeader('token', (new Token($this->config, $this->cache))->getToken($this->updatingToken));
228
        }
229
        return $this->headers;
230
    }
231
232
    /**
233
     * 添加头部信息
234
     *
235
     * @param string|array $key
236
     * @param null|string $value
237
     * @return $this
238
     * @author herry.yao <[email protected]>
239
     * @version 1.2.2
240
     * @date 2020-08-05
241
     */
242
    public function addHeader($key, $value = null)
243
    {
244
        $headers = is_array($key) ? $key : [$key => $value];
245
        $this->headers = array_merge($this->headers, $headers);
246
        return $this;
247
    }
248
249
    /**
250
     * 获取超时时间
251
     *
252
     * @return int
253
     * @author herry.yao <[email protected]>
254
     * @version 1.2.2
255
     * @date 2020-08-05
256
     */
257
    protected function getTimeout(): int
258
    {
259
        return $this->timeout;
260
    }
261
262
    /**
263
     * 设置超时时间
264
     *
265
     * @param int $timeout
266
     * @return $this
267
     * @author herry.yao <[email protected]>
268
     * @version 1.2.2
269
     * @date 2020-08-05
270
     */
271
    public function setTimeout(int $timeout)
272
    {
273
        $this->timeout = $timeout;
274
        return $this;
275
    }
276
277
    /**
278
     * 接口请求方式
279
     *
280
     * @return string
281
     * @author herry.yao <[email protected]>
282
     * @version 1.2.2
283
     * @date 2020-08-05
284
     */
285
    abstract public function getRequestMethod(): string;
286
287
    /**
288
     * 接口请求参数
289
     *
290
     * @return Request
291
     * @author herry.yao <[email protected]>
292
     * @version 1.2.2
293
     * @date 2020-08-05
294
     */
295
    public function getRequestParam(): Request
296
    {
297
        return $this->request;
298
    }
299
300
    /**
301
     * 接口请求地址
302
     *
303
     * @return string
304
     * @author herry.yao <[email protected]>
305
     * @version 1.2.2
306
     * @date 2020-08-05
307
     */
308
    abstract public function getRequestPath(): string;
309
310
    /**
311
     * 魔术方法 获取值
312
     *
313
     * @param $name
314
     * @return Request|null
315
     * @author herry.yao <[email protected]>
316
     * @version 1.2.2
317
     * @date 2020-08-05
318
     */
319
    public function __get($name)
320
    {
321
        $class = get_class($this) . '\\' . underline_to_hump($name);
322
        if (class_exists($class)) {
323
            $this->{$name} = new $class($this->config, $this->cache);
324
            return $this->{$name};
325
        }
326
        return null;
327
    }
328
329
    /**
330
     * 魔术方法赋值
331
     *
332
     * @param $name
333
     * @param $value
334
     */
335
    public function __set($name, $value)
336
    {
337
        if (!is_object($value)) {
338
            $name = underline_to_hump($name);
339
            $this->request->{$name}($name);
340
        } else {
341
            $this->{$name} = $value;
342
        }
343
    }
344
345
    /**
346
     * 方法实例
347
     *
348
     * @param $name
349
     * @param $arguments
350
     * @return Sobot
351
     * @version 1.2.2
352
     * @date 2020-08-05
353
     * @author herry.yao <[email protected]>
354
     */
355
    public function __call($name, $arguments)
356
    {
357
        $this->request->{$name}(...$arguments);
358
        return $this;
359
    }
360
}