AbstractAPI.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. /*
  3. * This file is part of the overtrue/wechat.
  4. *
  5. * (c) overtrue <i@overtrue.me>
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. /**
  11. * AbstractAPI.php.
  12. *
  13. * This file is part of the wechat-components.
  14. *
  15. * (c) overtrue <i@overtrue.me>
  16. *
  17. * This source file is subject to the MIT license that is bundled
  18. * with this source code in the file LICENSE.
  19. */
  20. namespace EasyWeChat\Core;
  21. use EasyWeChat\Core\Exceptions\HttpException;
  22. use EasyWeChat\Support\Collection;
  23. use EasyWeChat\Support\Log;
  24. use GuzzleHttp\Middleware;
  25. use GuzzleHttp\Psr7\Uri;
  26. use Psr\Http\Message\RequestInterface;
  27. use Psr\Http\Message\ResponseInterface;
  28. /**
  29. * Class AbstractAPI.
  30. */
  31. abstract class AbstractAPI
  32. {
  33. /**
  34. * Http instance.
  35. *
  36. * @var \EasyWeChat\Core\Http
  37. */
  38. protected $http;
  39. /**
  40. * The request token.
  41. *
  42. * @var \EasyWeChat\Core\AccessToken
  43. */
  44. protected $accessToken;
  45. const GET = 'get';
  46. const POST = 'post';
  47. const JSON = 'json';
  48. /**
  49. * @var int
  50. */
  51. protected static $maxRetries = 2;
  52. /**
  53. * Constructor.
  54. *
  55. * @param \EasyWeChat\Core\AccessToken $accessToken
  56. */
  57. public function __construct(AccessToken $accessToken)
  58. {
  59. $this->setAccessToken($accessToken);
  60. }
  61. /**
  62. * Return the http instance.
  63. *
  64. * @return \EasyWeChat\Core\Http
  65. */
  66. public function getHttp()
  67. {
  68. if (is_null($this->http)) {
  69. $this->http = new Http();
  70. }
  71. if (0 === count($this->http->getMiddlewares())) {
  72. $this->registerHttpMiddlewares();
  73. }
  74. return $this->http;
  75. }
  76. /**
  77. * Set the http instance.
  78. *
  79. * @param \EasyWeChat\Core\Http $http
  80. *
  81. * @return $this
  82. */
  83. public function setHttp(Http $http)
  84. {
  85. $this->http = $http;
  86. return $this;
  87. }
  88. /**
  89. * Return the current accessToken.
  90. *
  91. * @return \EasyWeChat\Core\AccessToken
  92. */
  93. public function getAccessToken()
  94. {
  95. return $this->accessToken;
  96. }
  97. /**
  98. * Set the request token.
  99. *
  100. * @param \EasyWeChat\Core\AccessToken $accessToken
  101. *
  102. * @return $this
  103. */
  104. public function setAccessToken(AccessToken $accessToken)
  105. {
  106. $this->accessToken = $accessToken;
  107. return $this;
  108. }
  109. /**
  110. * @param int $retries
  111. */
  112. public static function maxRetries($retries)
  113. {
  114. self::$maxRetries = abs($retries);
  115. }
  116. /**
  117. * Parse JSON from response and check error.
  118. *
  119. * @param string $method
  120. * @param array $args
  121. *
  122. * @return \EasyWeChat\Support\Collection | null
  123. *
  124. * @throws \EasyWeChat\Core\Exceptions\HttpException
  125. */
  126. public function parseJSON($method, array $args)
  127. {
  128. $http = $this->getHttp();
  129. $contents = $http->parseJSON(call_user_func_array([$http, $method], $args));
  130. if (empty($contents)) {
  131. return null;
  132. }
  133. $this->checkAndThrow($contents);
  134. return new Collection($contents);
  135. }
  136. /**
  137. * Register Guzzle middlewares.
  138. */
  139. protected function registerHttpMiddlewares()
  140. {
  141. // log
  142. $this->http->addMiddleware($this->logMiddleware());
  143. // retry
  144. $this->http->addMiddleware($this->retryMiddleware());
  145. // access token
  146. $this->http->addMiddleware($this->accessTokenMiddleware());
  147. }
  148. /**
  149. * Attache access token to request query.
  150. *
  151. * @return \Closure
  152. */
  153. protected function accessTokenMiddleware()
  154. {
  155. return function (callable $handler) {
  156. return function (RequestInterface $request, array $options) use ($handler) {
  157. if (!$this->accessToken) {
  158. return $handler($request, $options);
  159. }
  160. $field = $this->accessToken->getQueryName();
  161. $token = $this->accessToken->getToken();
  162. $request = $request->withUri(Uri::withQueryValue($request->getUri(), $field, $token));
  163. return $handler($request, $options);
  164. };
  165. };
  166. }
  167. /**
  168. * Log the request.
  169. *
  170. * @return \Closure
  171. */
  172. protected function logMiddleware()
  173. {
  174. return Middleware::tap(function (RequestInterface $request, $options) {
  175. Log::debug("Request: {$request->getMethod()} {$request->getUri()} ".json_encode($options));
  176. Log::debug('Request headers:'.json_encode($request->getHeaders()));
  177. });
  178. }
  179. /**
  180. * Return retry middleware.
  181. *
  182. * @return \Closure
  183. */
  184. protected function retryMiddleware()
  185. {
  186. return Middleware::retry(function (
  187. $retries,
  188. RequestInterface $request,
  189. ResponseInterface $response = null
  190. ) {
  191. // Limit the number of retries to 2
  192. if ($retries <= self::$maxRetries && $response && $body = $response->getBody()) {
  193. // Retry on server errors
  194. if (false !== stripos($body, 'errcode') && (false !== stripos($body, '40001') || false !== stripos($body, '42001'))) {
  195. $field = $this->accessToken->getQueryName();
  196. $token = $this->accessToken->getToken(true);
  197. $request = $request->withUri($newUri = Uri::withQueryValue($request->getUri(), $field, $token));
  198. Log::debug("Retry with Request Token: {$token}");
  199. Log::debug("Retry with Request Uri: {$newUri}");
  200. return true;
  201. }
  202. }
  203. return false;
  204. });
  205. }
  206. /**
  207. * Check the array data errors, and Throw exception when the contents contains error.
  208. *
  209. * @param array $contents
  210. *
  211. * @throws \EasyWeChat\Core\Exceptions\HttpException
  212. */
  213. protected function checkAndThrow(array $contents)
  214. {
  215. if (isset($contents['errcode']) && 0 !== $contents['errcode']) {
  216. if (empty($contents['errmsg'])) {
  217. $contents['errmsg'] = 'Unknown';
  218. }
  219. throw new HttpException($contents['errmsg'], $contents['errcode']);
  220. }
  221. }
  222. }