Skip to content

Commit 746d3e1

Browse files
committed
feat(zohoCliq): implement caching for access token using PSR simple cache
- Replace static in-memory access token with PSR simple cache implementation - Add FileCache as default cache and allow custom cache injection - Store access token with expiration to reduce redundant API calls - Update token refresh logic to use cache and refresh when token is missing or expired - Improve response status check for 401 Unauthorized to refresh token accordingly Signed-off-by: guanguans <ityaozm@gmail.com>
1 parent 4d4e7a7 commit 746d3e1

2 files changed

Lines changed: 53 additions & 25 deletions

File tree

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ parameters:
7373
# - identifier: typePerfect.noMixedMethodCaller
7474
- identifier: argument.templateType
7575
- identifier: argument.type
76+
- identifier: cast.int
7677
- identifier: cast.string
7778
- identifier: encapsedStringPart.nonString
7879
- identifier: logicalAnd.resultUnused

src/ZohoCliq/Authenticator.php

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@
1414
namespace Guanguans\Notify\ZohoCliq;
1515

1616
use Guanguans\Notify\Foundation\Authenticators\NullAuthenticator;
17+
use Guanguans\Notify\Foundation\Caches\FileCache;
1718
use Guanguans\Notify\Foundation\Client;
1819
use Guanguans\Notify\Foundation\Exceptions\RequestException;
1920
use Guanguans\Notify\ZohoCliq\Messages\AccessTokenMessage;
2021
use GuzzleHttp\Middleware;
2122
use Psr\Http\Message\RequestInterface;
2223
use Psr\Http\Message\ResponseInterface;
24+
use Psr\SimpleCache\CacheInterface;
2325

2426
/**
27+
* @see https://github.com/w7corp/easywechat/blob/6.x/src/OfficialAccount/AccessToken.php
2528
* @see https://github.com/w7corp/easywechat/blob/6.x/src/OpenPlatform/ComponentAccessToken.php
29+
* @see https://github.com/w7corp/easywechat/blob/6.x/src/OpenWork/AuthorizerAccessToken.php
30+
* @see https://github.com/w7corp/easywechat/blob/6.x/src/Work/AccessToken.php
2631
*
2732
* ```
2833
* curl --location 'https://accounts.zoho.com/oauth/v2/token' \
@@ -41,20 +46,36 @@
4146
* --header 'Authorization: Zoho-oauthtoken 1000.80e9143983dcc190427cad7e8f029e25.cdc1c1043e5e7f0555fc14fc7faf3' \
4247
* ```
4348
*/
44-
class Authenticator extends NullAuthenticator
49+
class Authenticator extends NullAuthenticator implements \Stringable
4550
{
46-
private static ?string $accessToken;
51+
private CacheInterface $cache;
52+
private string $cacheKey;
4753
private Client $client;
4854

4955
public function __construct(
5056
private string $clientId,
5157
#[\SensitiveParameter]
5258
private string $clientSecret,
59+
?CacheInterface $cache = null,
60+
?string $cacheKey = null,
5361
?Client $client = null,
5462
) {
63+
$this->cache = $cache ?? new FileCache;
64+
$this->cacheKey = $cacheKey ?? "zoho_cliq.access_token.$clientId.$clientSecret";
5565
$this->client = $client ?? new Client;
5666
}
5767

68+
/**
69+
* @throws \GuzzleHttp\Exception\GuzzleException
70+
* @throws \JsonException
71+
* @throws \Psr\SimpleCache\InvalidArgumentException
72+
* @throws \ReflectionException
73+
*/
74+
public function __toString(): string
75+
{
76+
return $this->getToken();
77+
}
78+
5879
public function applyToMiddleware(callable $handler): callable
5980
{
6081
return array_reduce(
@@ -68,11 +89,6 @@ public function applyToMiddleware(callable $handler): callable
6889
);
6990
}
7091

71-
public static function flushAccessToken(): void
72-
{
73-
self::$accessToken = null;
74-
}
75-
7692
/**
7793
* @todo
7894
*/
@@ -87,7 +103,7 @@ private function authenticate(callable $handler): callable
87103
fn (RequestInterface $request): RequestInterface => $request->withHeader(
88104
'Authorization',
89105
// "Bearer xxx",
90-
"Bearer {$this->getAccessToken()}",
106+
"Bearer {$this->getToken()}",
91107
),
92108
)($handler);
93109
}
@@ -100,8 +116,8 @@ function (int $retries, RequestInterface &$request, ?ResponseInterface $response
100116
return false;
101117
}
102118

103-
if ($response?->getStatusCode() === 401) {
104-
$request = $request->withHeader('Authorization', "Bearer {$this->refreshAccessToken()}");
119+
if (401 === $response?->getStatusCode()) {
120+
$request = $request->withHeader('Authorization', "Bearer {$this->refreshToken()}");
105121

106122
return true;
107123
}
@@ -111,39 +127,50 @@ function (int $retries, RequestInterface &$request, ?ResponseInterface $response
111127
)($handler);
112128
}
113129

114-
private function refreshAccessToken(): string
115-
{
116-
self::flushAccessToken();
117-
118-
return $this->getAccessToken();
119-
}
120-
121130
/**
122-
* Temporary memory cache.
123-
*
124-
* @todo psr cache
131+
* @throws \GuzzleHttp\Exception\GuzzleException
132+
* @throws \JsonException
133+
* @throws \Psr\SimpleCache\InvalidArgumentException
134+
* @throws \ReflectionException
125135
*/
126-
private function getAccessToken(): string
136+
private function getToken(): string
127137
{
128-
return self::$accessToken ??= $this->fetchAccessToken();
138+
if (($token = $this->cache->get($this->cacheKey)) && \is_string($token)) {
139+
return $token;
140+
}
141+
142+
return $this->refreshToken();
129143
}
130144

131145
/**
132146
* @throws \GuzzleHttp\Exception\GuzzleException
133147
* @throws \JsonException
148+
* @throws \Psr\SimpleCache\InvalidArgumentException
134149
* @throws \ReflectionException
150+
*
151+
* ```json
152+
* {
153+
* "access_token": "1000.86e0701b6f279bfad7b6a05352dc304d.3106ea5d20401799c010212da3da1",
154+
* "scope": "ZohoCliq.Webhooks.CREATE",
155+
* "api_domain": "https://www.zohoapis.com",
156+
* "token_type": "Bearer",
157+
* "expires_in": 3600
158+
* }
159+
* ```
135160
*/
136-
private function fetchAccessToken(): string
161+
private function refreshToken(): string
137162
{
138163
$response = $this->client->send(AccessTokenMessage::make([
139164
'client_id' => $this->clientId,
140165
'client_secret' => $this->clientSecret,
141166
]));
142167

143-
if (!$accessToken = $response->json('access_token')) {
168+
if (!$token = $response->json('access_token')) {
144169
throw RequestException::create($response->request(), $response);
145170
}
146171

147-
return $accessToken;
172+
$this->cache->set($this->cacheKey, $token, abs((int) $response->json('expires_in') - 100));
173+
174+
return $token;
148175
}
149176
}

0 commit comments

Comments
 (0)