* * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ namespace EasyWeChat\Kernel; use EasyWeChat\Kernel\Contracts\AccessTokenInterface; use EasyWeChat\Kernel\Exceptions\HttpException; use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; use EasyWeChat\Kernel\Exceptions\RuntimeException; use EasyWeChat\Kernel\Traits\HasHttpRequests; use EasyWeChat\Kernel\Traits\InteractsWithCache; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; /** * Class AccessToken. * * @author overtrue */ abstract class AccessToken implements AccessTokenInterface { use HasHttpRequests; use InteractsWithCache; /** * @var \EasyWeChat\Kernel\ServiceContainer */ protected $app; /** * @var string */ protected $requestMethod = 'GET'; /** * @var string */ protected $endpointToGetToken; /** * @var string */ protected $queryName; /** * @var array */ protected $token; /** * @var string */ protected $tokenKey = 'access_token'; /** * @var string */ protected $cachePrefix = 'easywechat.kernel.access_token.'; /** * AccessToken constructor. * * @param \EasyWeChat\Kernel\ServiceContainer $app */ public function __construct(ServiceContainer $app) { $this->app = $app; } public function getLastToken(): array { return $this->token; } /** * @throws \EasyWeChat\Kernel\Exceptions\HttpException * @throws \Psr\SimpleCache\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException */ public function getRefreshedToken(): array { return $this->getToken(true); } /** * @throws \EasyWeChat\Kernel\Exceptions\HttpException * @throws \Psr\SimpleCache\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException */ public function getToken(bool $refresh = false): array { $cacheKey = $this->getCacheKey(); $cache = $this->getCache(); if (!$refresh && $cache->has($cacheKey) && $result = $cache->get($cacheKey)) { return $result; } /** @var array $token */ $token = $this->requestToken($this->getCredentials(), true); $this->setToken($token[$this->tokenKey], $token['expires_in'] ?? 7200); $this->token = $token; $this->app->events->dispatch(new Events\AccessTokenRefreshed($this)); return $token; } /** * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException * @throws \Psr\SimpleCache\InvalidArgumentException */ public function setToken(string $token, int $lifetime = 7200): AccessTokenInterface { $this->getCache()->set($this->getCacheKey(), [ $this->tokenKey => $token, 'expires_in' => $lifetime, ], $lifetime); if (!$this->getCache()->has($this->getCacheKey())) { throw new RuntimeException('Failed to cache access token.'); } return $this; } /** * @throws \EasyWeChat\Kernel\Exceptions\HttpException * @throws \Psr\SimpleCache\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException */ public function refresh(): AccessTokenInterface { $this->getToken(true); return $this; } /** * @param bool $toArray * * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string * * @throws \EasyWeChat\Kernel\Exceptions\HttpException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException */ public function requestToken(array $credentials, $toArray = false) { $response = $this->sendRequest($credentials); $result = json_decode($response->getBody()->getContents(), true); $formatted = $this->castResponseToType($response, $this->app['config']->get('response_type')); if (empty($result[$this->tokenKey])) { throw new HttpException('Request access_token fail: '.json_encode($result, JSON_UNESCAPED_UNICODE), $response, $formatted); } return $toArray ? $result : $formatted; } /** * @throws \EasyWeChat\Kernel\Exceptions\HttpException * @throws \Psr\SimpleCache\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException */ public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface { parse_str($request->getUri()->getQuery(), $query); $query = http_build_query(array_merge($this->getQuery(), $query)); return $request->withUri($request->getUri()->withQuery($query)); } /** * Send http request. * * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \GuzzleHttp\Exception\GuzzleException */ protected function sendRequest(array $credentials): ResponseInterface { $options = [ ('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials, ]; return $this->setHttpClient($this->app['http_client'])->request($this->getEndpoint(), $this->requestMethod, $options); } /** * @return string */ protected function getCacheKey() { return $this->cachePrefix.md5(json_encode($this->getCredentials())); } /** * The request query will be used to add to the request. * * @throws \EasyWeChat\Kernel\Exceptions\HttpException * @throws \Psr\SimpleCache\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException */ protected function getQuery(): array { return [$this->queryName ?? $this->tokenKey => $this->getToken()[$this->tokenKey]]; } /** * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException */ public function getEndpoint(): string { if (empty($this->endpointToGetToken)) { throw new InvalidArgumentException('No endpoint for access token request.'); } return $this->endpointToGetToken; } /** * @return string */ public function getTokenKey() { return $this->tokenKey; } /** * Credential for get token. */ abstract protected function getCredentials(): array; }